codetools: code completion: support class constructors/destructors, patch #25130 from Ondrej Pokorny

git-svn-id: trunk@49996 -
This commit is contained in:
mattias 2015-10-08 14:20:54 +00:00
parent 70eed01820
commit b605c39631
6 changed files with 163 additions and 39 deletions

View File

@ -88,7 +88,7 @@ uses
FileProcs, CodeToolsStrConsts, StdCodeTools,
CodeTree, CodeAtom, CodeCache, CustomCodeTool, PascalParserTool, MethodJumpTool,
FindDeclarationTool, KeywordFuncLists, CodeToolsStructs, BasicCodeTools,
LinkScanner, SourceChanger, CodeGraph,
LinkScanner, SourceChanger, CodeGraph, PascalReaderTool,
{$IFDEF EnableCodeCompleteTemplates}
CodeCompletionTemplater,
{$ENDIF}
@ -140,7 +140,7 @@ type
FSetPropertyVariablename: string;
FSetPropertyVariableIsPrefix: Boolean;
FSetPropertyVariableUseConst: Boolean;
FJumpToProcName: string;
FJumpToProcHead: TPascalMethodHeader;
NewClassSectionIndent: array[TPascalClassSection] of integer;
NewClassSectionInsertPos: array[TPascalClassSection] of integer;
fFullTopLvlName: string;// used by OnTopLvlIdentifierFound
@ -457,13 +457,13 @@ begin
end;
// ToDo: check ancestor procs too
// search in current class
Result:=(FindProcNode(FCompletingFirstEntryNode,NameAndParamsUpCase,[phpInUpperCase])<>nil);
Result:=(FindProcNode(FCompletingFirstEntryNode,NameAndParamsUpCase,mgMethod,[phpInUpperCase])<>nil);
end;
procedure TCodeCompletionCodeTool.SetCodeCompleteClassNode(const AClassNode: TCodeTreeNode);
begin
FreeClassInsertionList;
FJumpToProcName:='';
FJumpToProcHead.Name:='';
FCodeCompleteClassNode:=AClassNode;
if CodeCompleteClassNode=nil then begin
FCompletingFirstEntryNode:=nil;
@ -1500,7 +1500,8 @@ begin
MemSizeString(FSetPropertyVariablename)
+PtrUInt(SizeOf(FSetPropertyVariableIsPrefix))
+PtrUInt(SizeOf(FSetPropertyVariableUseConst))
+MemSizeString(FJumpToProcName)
+MemSizeString(FJumpToProcHead.Name)
+PtrUInt(SizeOf(FJumpToProcHead.Group))
+length(NewClassSectionIndent)*SizeOf(integer)
+length(NewClassSectionInsertPos)*SizeOf(integer)
+MemSizeString(fFullTopLvlName));
@ -8349,8 +8350,9 @@ procedure TCodeCompletionCodeTool.GuessProcDefBodyMapping(ProcDefNodes,
NewNodeExt.Node:=ProcNode;
NewNodeExt.Txt:=ExtractProcName(ProcNode,[phpWithoutClassName]);
NewNodeExt.Data:=NodeExt;
NewNodeExt.Flags:=Integer(ExtractProcedureGroup(ProcNode));
if Result=nil then
Result:=TAVLTree.Create(@CompareCodeTreeNodeExt);
Result:=TAVLTree.Create(@CompareCodeTreeNodeExtMethodHeaders);
Result.Add(NewNodeExt);
end;
AVLNodeExt:=NodeExtTree.FindSuccessor(AVLNodeExt);
@ -8544,15 +8546,16 @@ var
{$ENDIF}
ProcCode:=Beauty.BeautifyProc(ProcCode,Indent,ANodeExt.ExtTxt3='');
FSourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,ProcCode);
if FJumpToProcName='' then begin
if FJumpToProcHead.Name='' then begin
// remember one proc body to jump to after the completion
FJumpToProcName:=ANodeExt.Txt;
if System.Pos('.',FJumpToProcName)<1 then
FJumpToProcName:=TheClassName+'.'+FJumpToProcName;
if FJumpToProcName[length(FJumpToProcName)]<>';' then
FJumpToProcName:=FJumpToProcName+';';
FJumpToProcHead.Name:=ANodeExt.Txt;
FJumpToProcHead.Group:=TPascalMethodGroup(ANodeExt.Flags);
if System.Pos('.',FJumpToProcHead.Name)<1 then
FJumpToProcHead.Name:=TheClassName+'.'+FJumpToProcHead.Name;
if FJumpToProcHead.Name[length(FJumpToProcHead.Name)]<>';' then
FJumpToProcHead.Name:=FJumpToProcHead.Name+';';
{$IFDEF CTDEBUG}
DebugLn('CreateMissingClassProcBodies FJumpToProcName="',FJumpToProcName,'"');
DebugLn('CreateMissingClassProcBodies FJumpToProcHead.Name="',FJumpToProcHead.Name,'"');
{$ENDIF}
end;
end;
@ -8615,7 +8618,9 @@ var
if NextAVLNode<>nil then begin
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
ANodeExt2:=TCodeTreeNodeExtension(NextAVLNode.Data);
if CompareTextIgnoringSpace(ANodeExt.Txt,ANodeExt2.Txt,false)=0 then
if SameMethodHeaders(ANodeExt.Txt, TPascalMethodGroup(ANodeExt.Flags),
ANodeExt2.Txt, TPascalMethodGroup(ANodeExt2.Flags))
then
begin
// proc redefined -> error
if ANodeExt.Node.StartPos>ANodeExt2.Node.StartPos then begin
@ -8937,7 +8942,7 @@ begin
begin
// search alphabetically nearest proc body
ExistingNode:=ProcBodyNodes.FindNearest(MissingNode.Data);
cmp:=CompareCodeTreeNodeExt(ExistingNode.Data,MissingNode.Data);
cmp:=CompareCodeTreeNodeExtMethodHeaders(ExistingNode.Data,MissingNode.Data);
if (cmp<0) then begin
AnAVLNode:=ProcBodyNodes.FindSuccessor(ExistingNode);
if AnAVLNode<>nil then begin
@ -9043,9 +9048,9 @@ begin
FreeClassInsertionList;
end;
if FJumpToProcName<>'' then begin
if FJumpToProcHead.Name<>'' then begin
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Jump to new proc body ... "',FJumpToProcName,'"');
DebugLn('TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Jump to new proc body ... "',FJumpToProcHead.Name,'"');
{$ENDIF}
// there was a new proc body
// -> find it and jump to
@ -9061,9 +9066,9 @@ begin
FCodeCompleteClassNode:=FindClassNode(CursorNode,CurClassName,true,false);
if CodeCompleteClassNode=nil then
RaiseException('oops, I lost your class');
ProcNode:=FindProcNode(CursorNode,FJumpToProcName,[phpInUpperCase,phpIgnoreForwards]);
ProcNode:=FindProcNode(CursorNode,FJumpToProcHead,[phpInUpperCase,phpIgnoreForwards]);
if ProcNode=nil then begin
debugln(['TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Proc="',FJumpToProcName,'"']);
debugln(['TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Proc="',FJumpToProcHead.Name,'"']);
RaiseException(ctsNewProcBodyNotFound);
end;
Result:=FindJumpPointInProcNode(ProcNode,NewPos,NewTopLine);

View File

@ -43,7 +43,7 @@ interface
uses
Classes, SysUtils, math, FileProcs, CodeToolsStrConsts, CodeTree, CodeAtom,
CodeCache, CustomCodeTool,
CodeCache, CustomCodeTool, PascalReaderTool,
PascalParserTool, CodeCompletionTool, KeywordFuncLists, BasicCodeTools,
LinkScanner, AVL_Tree, SourceChanger,
FindDeclarationTool;
@ -843,7 +843,7 @@ var
else
ProcHead:='';
ProcHead:=ProcHead+ProcName+BaseParamList;
ConflictProcNode:=FindProcNode(ContextNode,ProcHead,
ConflictProcNode:=FindProcNode(ContextNode,ProcHead,mgMethod,
ShortProcFormat+[phpIgnoreForwards]);
Result:=ConflictProcNode<>nil;
if Result then begin

View File

@ -9517,6 +9517,12 @@ begin
exit;
end;
if FoundContext.Tool.NodeIsClassConstructorOrDestructor(FoundContext.Node) then
begin
Result:=ifrProceedSearch;
Exit;
end;
if (not (fdfCollect in Params.Flags))
and CallHasEmptyParamsAndFoundProcFits then begin
// call has brackets without params (e.g. writeln() )

View File

@ -1171,6 +1171,8 @@ begin
Ident:=@FoundContext.Tool.Src[FoundContext.Node.StartPos];
ctnProcedure,ctnProcedureHead:
//do not list class constructors and destructors
if not FoundContext.Tool.NodeIsClassConstructorOrDestructor(FoundContext.Node) then
begin
Ident:=FoundContext.Tool.GetProcNameIdentifier(FoundContext.Node);
NewItem := CurrentIdentifierList.FindIdentifier(Ident,true);

View File

@ -42,7 +42,7 @@ uses
{$ENDIF}
Classes, SysUtils, FileProcs, LazFileUtils, CodeTree, PascalParserTool,
StdCodeTools, KeywordFuncLists, BasicCodeTools,
LinkScanner, CodeCache, AVL_Tree;
LinkScanner, CodeCache, PascalReaderTool, AVL_Tree;
type
@ -274,16 +274,16 @@ const
// search for a proc node with same name and jump to difference in param list
// returns true on jumped, false if no target proc found
var
SearchedProcHead: string;
SearchedProcHead: TPascalMethodHeader;
ProcNode: TCodeTreeNode;
begin
Result:=false;
if SearchForProcNode=nil then exit;
SearchedProcHead:=ExtractProcHead(SearchForProcNode,SearchForProcAttr);
SearchedProcHead:=ExtractProcHeadWithGroup(SearchForProcNode,SearchForProcAttr);
{$IFDEF CTDEBUG}
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Searching ',SearchForProcNode.DescAsString,' "',SearchedProcHead,'" ',ProcHeadAttributesToStr(SearchForProcAttr));
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Searching ',SearchForProcNode.DescAsString,' "',SearchedProcHead.Head,'" ',ProcHeadAttributesToStr(SearchForProcAttr));
{$ENDIF}
if SearchedProcHead='' then exit;
if SearchedProcHead.Name='' then exit;
ProcNode:=FindProcNode(StartNode,SearchedProcHead,SearchInProcAttr);
{$IFDEF CTDEBUG}
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Found:',dbgs(ProcNode<>nil));
@ -301,11 +301,11 @@ const
phpWithComments];
SearchForProcAttr:=SearchForProcAttr+[phpWithoutBrackets,
phpWithoutParamList];
SearchedProcHead:=ExtractProcHead(SearchForProcNode,SearchForProcAttr);
SearchedProcHead:=ExtractProcHeadWithGroup(SearchForProcNode,SearchForProcAttr);
{$IFDEF CTDEBUG}
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Searching without params "',SearchedProcHead,'"');
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Searching without params "',SearchedProcHead.Name,'"');
{$ENDIF}
if SearchedProcHead='' then exit;
if SearchedProcHead.Name='' then exit;
ProcNode:=FindProcNode(StartNode,SearchedProcHead,SearchForProcAttr);
{$IFDEF CTDEBUG}
DebugLn('TMethodJumpingCodeTool.FindJumpPoint.FindBestProcNode Found:',dbgs(ProcNode<>nil));
@ -789,7 +789,7 @@ var CurProcName: string;
CurClassName: String;
begin
//debugln(['TMethodJumpingCodeTool.GatherProcNodes START']);
Result:=TAVLTree.Create(@CompareCodeTreeNodeExt);
Result:=TAVLTree.Create(@CompareCodeTreeNodeExtMethodHeaders);
if (StartNode=nil) or (StartNode.Parent=nil) then exit;
ANode:=StartNode;
while (ANode<>nil) do begin
@ -825,6 +825,7 @@ begin
with NewNodeExt do begin
Node:=ANode;
Txt:=CurProcName;
Flags:=Ord(ExtractProcedureGroup(ANode));
end;
Result.Add(NewNodeExt);
end;
@ -961,7 +962,7 @@ function TMethodJumpingCodeTool.FindSubProcPath(SubProcPath: TStrings;
Result:=nil;
if (PathIndex>SubProcPath.Count) or (StartNode=nil) then exit;
ProcHead:=SubProcPath[PathIndex];
ProcNode:=FindProcNode(StartNode,ProcHead,Attr);
ProcNode:=FindProcNode(StartNode,ProcHead,mgMethod,Attr);
//DebugLn('TMethodJumpingCodeTool.SearchSubProcPath A ProcHead="',ProcHead,'" Found=',dbgs(ProcNode<>nil));
if ProcNode=nil then exit;
if PathIndex=SubProcPath.Count-1 then begin

View File

@ -57,6 +57,15 @@ type
epriInDirective
);
//the scope groups of pascal methods.
//please note that Destructor is principally a method and thus is not listed here -> you cannot define "procedure Destroy;" and "destructor Destroy" in one class
TPascalMethodGroup = (mgMethod, mgConstructor, mgClassConstructor, mgClassDestructor);
TPascalMethodHeader = record
Name: string;
Group: TPascalMethodGroup;
end;
TOnEachPRIdentifier = procedure(Sender: TPascalParserTool;
IdentifierCleanPos: integer; Range: TEPRIRange;
Node: TCodeTreeNode; Data: Pointer; var Abort: boolean) of object;
@ -121,6 +130,8 @@ type
Attr: TProcHeadAttributes): string;
function ExtractProcHead(ProcNode: TCodeTreeNode;
Attr: TProcHeadAttributes): string;
function ExtractProcHeadWithGroup(ProcNode: TCodeTreeNode;
Attr: TProcHeadAttributes): TPascalMethodHeader;
function ExtractProcedureHeader(CursorPos: TCodeXYPosition;
Attributes: TProcHeadAttributes; var ProcHead: string): boolean;
function ExtractClassNameOfProcNode(ProcNode: TCodeTreeNode;
@ -129,7 +140,10 @@ type
ProcSpec: TProcedureSpecifier): boolean;
function GetProcNameIdentifier(ProcNode: TCodeTreeNode): PChar;
function FindProcNode(StartNode: TCodeTreeNode; const AProcHead: string;
Attr: TProcHeadAttributes): TCodeTreeNode;
AProcSpecType: TPascalMethodGroup;
Attr: TProcHeadAttributes): TCodeTreeNode; overload;
function FindProcNode(StartNode: TCodeTreeNode; const AProcHead: TPascalMethodHeader;
Attr: TProcHeadAttributes): TCodeTreeNode; overload;
function FindCorrespondingProcNode(ProcNode: TCodeTreeNode;
Attr: TProcHeadAttributes = [phpWithoutClassKeyword,phpWithoutClassName]
): TCodeTreeNode;
@ -138,6 +152,7 @@ type
): TCodeTreeNode;
function FindProcBody(ProcNode: TCodeTreeNode): TCodeTreeNode;
function ProcBodyIsEmpty(ProcNode: TCodeTreeNode): boolean;
function ExtractProcedureGroup(ProcNode: TCodeTreeNode): TPascalMethodGroup;
procedure MoveCursorToFirstProcSpecifier(ProcNode: TCodeTreeNode);
function MoveCursorToProcSpecifier(ProcNode: TCodeTreeNode;
ProcSpec: TProcedureSpecifier): boolean;
@ -156,6 +171,7 @@ type
function NodeIsMethodBody(ProcNode: TCodeTreeNode): boolean;
function GetMethodOfBody(Node: TCodeTreeNode): TCodeTreeNode;
function NodeIsFunction(ProcNode: TCodeTreeNode): boolean;
function NodeIsClassConstructorOrDestructor(ProcNode: TCodeTreeNode): boolean;
function NodeIsConstructor(ProcNode: TCodeTreeNode): boolean;
function NodeIsDestructor(ProcNode: TCodeTreeNode): boolean;
function NodeIsForwardProc(ProcNode: TCodeTreeNode): boolean;
@ -267,8 +283,54 @@ type
procedure CalcMemSize(Stats: TCTMemStats); override;
end;
function CompareMethodHeaders(const Method1Name: string; Method1Group: TPascalMethodGroup;
const Method2Name: string; Method2Group: TPascalMethodGroup): Integer; overload;
function CompareMethodHeaders(const Method1Head: TPascalMethodHeader; const Method2Head: TPascalMethodHeader): Integer; overload;
function SameMethodHeaders(const Method1Name: string; Method1Group: TPascalMethodGroup;
const Method2Name: string; Method2Group: TPascalMethodGroup): Boolean; overload;
function SameMethodHeaders(const Method1Head: TPascalMethodHeader; const Method2Head: TPascalMethodHeader): Boolean; overload;
function CompareCodeTreeNodeExtMethodHeaders(NodeData1, NodeData2: pointer): integer;
implementation
function CompareMethodHeaders(const Method1Name: string;
Method1Group: TPascalMethodGroup; const Method2Name: string;
Method2Group: TPascalMethodGroup): Integer;
begin
Result := (Ord(Method1Group) - Ord(Method2Group));
if Result <> 0 then exit;
Result := CompareTextIgnoringSpace(Method1Name,Method2Name,false);
end;
function CompareMethodHeaders(const Method1Head: TPascalMethodHeader;
const Method2Head: TPascalMethodHeader): Integer;
begin
Result := CompareMethodHeaders(Method1Head.Name, Method1Head.Group,
Method2Head.Name, Method2Head.Group);
end;
function SameMethodHeaders(const Method1Name: string;
Method1Group: TPascalMethodGroup; const Method2Name: string;
Method2Group: TPascalMethodGroup): Boolean;
begin
Result := CompareMethodHeaders(Method1Name, Method1Group, Method2Name, Method2Group) = 0;
end;
function SameMethodHeaders(const Method1Head: TPascalMethodHeader;
const Method2Head: TPascalMethodHeader): Boolean;
begin
Result := CompareMethodHeaders(Method1Head, Method2Head) = 0;
end;
function CompareCodeTreeNodeExtMethodHeaders(NodeData1, NodeData2: pointer): integer;
var
NodeExt1: TCodeTreeNodeExtension absolute NodeData1;
NodeExt2: TCodeTreeNodeExtension absolute NodeData2;
begin
Result:=CompareMethodHeaders(NodeExt1.Txt,TPascalMethodGroup(NodeExt1.Flags),NodeExt2.Txt,TPascalMethodGroup(NodeExt2.Flags));
end;
{ TPascalReaderTool }
procedure TPascalReaderTool.RaiseStrConstExpected;
@ -667,6 +729,13 @@ begin
Result:=Result+';';
end;
function TPascalReaderTool.ExtractProcHeadWithGroup(ProcNode: TCodeTreeNode;
Attr: TProcHeadAttributes): TPascalMethodHeader;
begin
Result.Name := ExtractProcHead(ProcNode, Attr);
Result.Group := ExtractProcedureGroup(ProcNode);
end;
function TPascalReaderTool.ExtractProcedureHeader(CursorPos: TCodeXYPosition;
Attributes: TProcHeadAttributes; var ProcHead: string): boolean;
var
@ -842,14 +911,15 @@ begin
end;
function TPascalReaderTool.FindProcNode(StartNode: TCodeTreeNode;
const AProcHead: string; Attr: TProcHeadAttributes): TCodeTreeNode;
const AProcHead: TPascalMethodHeader; Attr: TProcHeadAttributes): TCodeTreeNode;
// search in all next brothers for a Procedure Node with the Name ProcName
// if there are no further brothers and the parent is a section node
// ( e.g. 'interface', 'implementation', ...) or a class visibility node
// (e.g. 'public', 'private', ...) then the search will continue in the next
// section
var CurProcHead: string;
var
InClass: Boolean;
CurProcHead: TPascalMethodHeader;
begin
Result:=StartNode;
InClass:=FindClassOrInterfaceNode(StartNode)<>nil;
@ -860,10 +930,11 @@ begin
and (not ((phpIgnoreProcsWithBody in Attr)
and (FindProcBody(Result)<>nil))) then
begin
CurProcHead:=ExtractProcHead(Result,Attr);
CurProcHead:=ExtractProcHeadWithGroup(Result,Attr);
//DebugLn(['TPascalReaderTool.FindProcNode B "',CurProcHead,'" =? "',AProcHead,'" Result=',CompareTextIgnoringSpace(CurProcHead,AProcHead,false)]);
if (CurProcHead<>'')
and (CompareTextIgnoringSpace(CurProcHead,AProcHead,false)=0) then
if (CurProcHead.Name<>'') and
SameMethodHeaders(AProcHead, CurProcHead)
then
exit;
end;
end;
@ -875,12 +946,23 @@ begin
end;
end;
function TPascalReaderTool.FindProcNode(StartNode: TCodeTreeNode;
const AProcHead: string; AProcSpecType: TPascalMethodGroup;
Attr: TProcHeadAttributes): TCodeTreeNode;
var
ProcHead: TPascalMethodHeader;
begin
ProcHead.Name := AProcHead;
ProcHead.Group := AProcSpecType;
Result := FindProcNode(StartNode, ProcHead, Attr);
end;
function TPascalReaderTool.FindCorrespondingProcNode(ProcNode: TCodeTreeNode;
Attr: TProcHeadAttributes): TCodeTreeNode;
var
ClassNode: TCodeTreeNode;
StartNode: TCodeTreeNode;
ProcHead: String;
ProcHead: TPascalMethodHeader;
begin
Result:=nil;
// get ctnProcedure
@ -924,7 +1006,7 @@ begin
end;
if StartNode=nil then exit;
ProcHead:=ExtractProcHead(ProcNode,Attr);
ProcHead:=ExtractProcHeadWithGroup(ProcNode,Attr);
//debugln('TPascalReaderTool.FindCorrespondingProcNode StartNode=',StartNode.DescAsString,' ProcHead=',dbgstr(ProcHead),' ',dbgs(Attr),' ',StartNode.DescAsString);
Result:=FindProcNode(StartNode,ProcHead,Attr);
if Result=ProcNode then begin
@ -2405,6 +2487,28 @@ begin
Result:=ctnNone;
end;
function TPascalReaderTool.ExtractProcedureGroup(ProcNode: TCodeTreeNode
): TPascalMethodGroup;
begin
Result:=mgMethod;
if (ProcNode=nil) then exit;
if ProcNode.Desc=ctnProcedureHead then
ProcNode:=ProcNode.Parent;
if ProcNode.Desc<>ctnProcedure then exit;
MoveCursorToNodeStart(ProcNode);
ReadNextAtom;
if UpAtomIs('CLASS') then
begin
ReadNextAtom;
if UpAtomIs('CONSTRUCTOR') then
Result := mgClassConstructor;
if UpAtomIs('DESTRUCTOR') then
Result := mgClassDestructor;
end else
if UpAtomIs('CONSTRUCTOR') then
Result := mgConstructor
end;
function TPascalReaderTool.PositionInSourceName(CleanPos: integer): boolean;
var
NamePos: TAtomPosition;
@ -2733,6 +2837,12 @@ begin
Result:=true;
end;
function TPascalReaderTool.NodeIsClassConstructorOrDestructor(
ProcNode: TCodeTreeNode): boolean;
begin
Result := ExtractProcedureGroup(ProcNode) in [mgClassConstructor, mgClassDestructor];
end;
function TPascalReaderTool.NodeIsForwardType(TypeNode: TCodeTreeNode): boolean;
begin
Result:=false;