lazarus/components/codetools/codecompletiontool.pas
mattias e20310fe03 codetools: less verbose
git-svn-id: trunk@57773 -
2018-05-02 23:03:58 +00:00

9840 lines
363 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:
TCodeCompletionCodeTool enhances TMethodJumpingCodeTool.
Code Completion is
- complete properties
- complete property statements
- add private variables and private access methods
- add missing method bodies
- add useful statements
- add missing forward proc bodies
- add missing semicolons at end of procedures
- complete event assignments
- complete local variables
- complete local variables as parameter
- insert header comment for classes
ToDo:
-add code for array properties (TList, TFPList, array of, Pointer array)
TList:
property Items[Index: integer]: AType;
-> creates via dialog
property Items[Index: integer]: Type2 read GetItems write SetItems;
private FItems: TList;
private function GetItems(Index: integer): Type2;
begin
Result:=Type2(FItems[Index]);
end;
private procedure SetItems(Index: integer; const AValue: Type2);
begin
FItems[Index]:=Type2;
end;
public constructor Create;
begin
FItems:=TList.Create;
end;
public destructor Destroy; override;
begin
FItems.Free;
inherited Destroy;
end;
}
unit CodeCompletionTool;
{$ifdef FPC}{$mode objfpc}{$endif}{$H+}
interface
{$I codetools.inc}
{off $DEFINE CTDEBUG}
{$DEFINE VerboseCompletionAdds}
{off $DEFINE VerboseUpdateProcBodySignatures}
{off $DEFINE VerboseCompleteMethod}
{off $DEFINE VerboseCreateMissingClassProcBodies}
{off $DEFINE VerboseCompleteLocalVarAssign}
{off $DEFINE VerboseCompleteEventAssign}
{off $DEFINE EnableCodeCompleteTemplates}
{$DEFINE VerboseGetPossibleInitsForVariable}
{off $DEFINE VerboseGuessTypeOfIdentifier}
uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
// RTL + FCL
Classes, SysUtils, contnrs, Laz_AVL_Tree,
// CodeTools
FileProcs, CodeToolsStrConsts, StdCodeTools,
CodeTree, CodeAtom, CodeCache, CustomCodeTool, PascalParserTool, MethodJumpTool,
FindDeclarationTool, KeywordFuncLists, CodeToolsStructs, BasicCodeTools,
LinkScanner, SourceChanger, CodeGraph, PascalReaderTool,
{$IFDEF EnableCodeCompleteTemplates}
CodeCompletionTemplater,
{$ENDIF}
// LazUtils
LazFileUtils, LazDbgLog, AvgLvlTree;
type
TNewClassPart = (ncpPrivateProcs, ncpPrivateVars,
ncpProtectedProcs, ncpProtectedVars,
ncpPublicProcs, ncpPublicVars,
ncpPublishedProcs, ncpPublishedVars);
TNewVarLocation = (
ncpvPrivate,ncpvProtected,ncpvPublic,ncpvPublished,ncpvLocal
);
const
NewClassPartProcs = [ncpPrivateProcs,ncpProtectedProcs,ncpPublicProcs,ncpPublishedProcs];
NewClassPartVars = [ncpPrivateVars,ncpProtectedVars,ncpPublicVars,ncpPublishedVars];
NewClassPartVisibility: array[TNewClassPart] of TPascalClassSection = (
pcsPrivate, pcsPrivate,
pcsProtected, pcsProtected,
pcsPublic, pcsPublic,
pcsPublished, pcsPublished
);
PascalClassSectionToNodeDesc: array[TPascalClassSection] of TCodeTreeNodeDesc = (
ctnClassPrivate, // pcsPrivate
ctnClassProtected, // pcsProtected
ctnClassPublic, // pcsPublic
ctnClassPublished // pcsPublished
);
InsertClassSectionToNewProcClassPart: array[TInsertClassSection] of TNewClassPart = (
ncpPrivateProcs,
ncpProtectedProcs,
ncpPublicProcs,
ncpPublishedProcs
);
InsertClassSectionToNewVarClassPart: array[TInsertClassSection] of TNewClassPart = (
ncpPrivateVars,
ncpProtectedVars,
ncpPublicVars,
ncpPublishedVars
);
type
TCodeCreationDlgResult = record
Location: TCreateCodeLocation;
ClassSection: TInsertClassSection;
end;
{ TCodeCompletionCodeTool }
TCodeCompletionCodeTool = class(TMethodJumpingCodeTool)
private
FCompletingCursorNode: TCodeTreeNode;
FSourceChangeCache: TSourceChangeCache;
FCodeCompleteClassNode: TCodeTreeNode; // the class that is to be completed (ctnClass, ...)
FCompletingFirstEntryNode: TCodeTreeNode; // the first variable/method/GUID node in FCodeCompleteClassNode
FAddInheritedCodeToOverrideMethod: boolean;
FCompleteProperties: boolean;
FirstInsert: TCodeTreeNodeExtension; // list of insert requests
FSetPropertyVariablename: string;
FSetPropertyVariableIsPrefix: Boolean;
FSetPropertyVariableUseConst: Boolean;
FJumpToProcHead: TPascalMethodHeader;
NewClassSectionIndent: array[TPascalClassSection] of integer;
NewClassSectionInsertPos: array[TPascalClassSection] of integer;
fFullTopLvlName: string;// used by OnTopLvlIdentifierFound
fNewMainUsesSectionUnits: TAVLTree; // tree of AnsiString
procedure AddNewPropertyAccessMethodsToClassProcs(ClassProcs: TAVLTree;
const TheClassName: string);
procedure SetSetPropertyVariableIsPrefix(aValue: Boolean);
procedure SetSetPropertyVariablename(AValue: string);
procedure SetSetPropertyVariableUseConst(aValue: Boolean);
function UpdateProcBodySignature(ProcBodyNodes: TAVLTree;
const BodyNodeExt: TCodeTreeNodeExtension;
ProcAttrCopyDefToBody: TProcHeadAttributes; var ProcsCopied: boolean;
CaseSensitive: boolean): boolean;
function UpdateProcBodySignatures(ProcDefNodes, ProcBodyNodes: TAVLTree;
ProcAttrCopyDefToBody: TProcHeadAttributes; out ProcsCopied: boolean;
OnlyNode: TCodeTreeNode = nil): boolean;
procedure GuessProcDefBodyMapping(ProcDefNodes, ProcBodyNodes: TAVLTree;
MapByNameOnly, MapLastOne: boolean);
function GatherClassProcDefinitions(ClassNode: TCodeTreeNode;
RemoveAbstracts: boolean): TAVLTree;
function GatherClassProcBodies(ClassNode: TCodeTreeNode): TAVLTree;
procedure CheckForOverrideAndAddInheritedCode(
ANodeExt: TCodeTreeNodeExtension; Indent: integer);
function CompleteProperty(PropNode: TCodeTreeNode): boolean;
function GetFirstClassIdentifier(ClassNode: TCodeTreeNode): TCodeTreeNode;
procedure SetCodeCompleteClassNode(const AClassNode: TCodeTreeNode);
procedure SetCodeCompleteSrcChgCache(const AValue: TSourceChangeCache);
function OnTopLvlIdentifierFound(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
procedure RemoveNewMainUsesSectionUnit(p: PChar);
protected
procedure CheckWholeUnitParsed(var Node1, Node2: TCodeTreeNode;
Range: TLinkScannerRange = lsrEnd);
procedure FreeClassInsertionList;
procedure InsertNewClassParts(PartType: TNewClassPart);
function InsertAllNewClassParts: boolean;
function InsertClassHeaderComment: boolean;
function InsertMissingClassSemicolons: boolean;
function InsertAllNewUnitsToMainUsesSection: boolean;
function FindClassMethodsComment(StartPos: integer;
out CommentStart, CommentEnd: integer): boolean;
function FindProcAndClassNode(CursorNode: TCodeTreeNode; out ProcNode,
AClassNode: TCodeTreeNode): boolean;
function CreateMissingClassProcBodies(UpdateSignatures: boolean): boolean;
function ApplyChangesAndJumpToFirstNewProc(CleanPos: integer;
OldTopLine: integer; AddMissingProcBodies: boolean;
out NewPos: TCodeXYPosition; out NewTopLine, BlockTopLine, BlockBottomLine: integer): boolean;
function NodeExtIsVariable(ANodeExt: TCodeTreeNodeExtension): boolean;
function NodeExtHasVisibilty(ANodeExt: TCodeTreeNodeExtension;
Visibility: TPascalClassSection): boolean;
procedure FindInsertPositionForForwardProc(
SourceChangeCache: TSourceChangeCache;
ProcNode: TCodeTreeNode; out Indent, InsertPos: integer);
procedure FindInsertPositionForProcInterface(var Indent, InsertPos: integer;
SourceChangeCache: TSourceChangeCache);
function CheckLocalVarAssignmentSyntax(CleanCursorPos: integer;
out VarNameAtom,AssignmentOperator,TermAtom: TAtomPosition): boolean;
function CheckLocalVarForInSyntax(CleanCursorPos: integer;
out VarNameAtom,TermAtom: TAtomPosition): boolean;
function AddLocalVariable(CleanCursorPos: integer; OldTopLine: integer;
VariableName, VariableType, VariableTypeUnitName: string;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
SourceChangeCache: TSourceChangeCache;
CleanLevelPos: integer = 0): boolean;
procedure AdjustCursor(OldCodePos: TCodePosition; OldTopLine: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer);
procedure AddNeededUnitToMainUsesSection(AnUnitName: PChar);
function AddMethodCompatibleToProcType(AClassNode: TCodeTreeNode;
const AnEventName: string; ProcContext: TFindContext; out
MethodDefinition: string; out MethodAttr: TProcHeadAttributes;
SourceChangeCache: TSourceChangeCache; Interactive: Boolean): Boolean;
procedure AddProcedureCompatibleToProcType(
const NewProcName: string; ProcContext: TFindContext; out
MethodDefinition: string; out MethodAttr: TProcHeadAttributes;
SourceChangeCache: TSourceChangeCache;
CursorNode: TCodeTreeNode = nil);
function CompleteClass(AClassNode: TCodeTreeNode;
CleanCursorPos, OldTopLine: integer;
CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine, BlockTopLine, BlockBottomLine: integer): boolean;
function CompleteForwardProcs(CursorPos: TCodeXYPosition;
ProcNode, CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine, BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
function CompleteVariableAssignment(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache; Interactive: Boolean): boolean;
function CompleteEventAssignment(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode;
out IsEventAssignment: boolean;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache; Interactive: Boolean): boolean;
function CompleteVariableForIn(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache; {%H-}Interactive: Boolean): boolean;
function CompleteIdentifierByParameter(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache; Interactive: Boolean): boolean;
function CompleteMethodByBody(CleanCursorPos, OldTopLine: integer;
CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
function CreateParamListFromStatement(CursorNode: TCodeTreeNode;
BracketOpenPos: integer;
out CleanList: string): string;
function CompleteProcByCall(CleanCursorPos, OldTopLine: integer;
CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine, BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
protected
procedure DoDeleteNodes(StartNode: TCodeTreeNode); override;
public
constructor Create;
function CompleteCode(CursorPos: TCodeXYPosition; OldTopLine: integer;
out NewPos: TCodeXYPosition; out NewTopLine, BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache;
Interactive: Boolean): boolean;
function CreateVariableForIdentifier(CursorPos: TCodeXYPosition; OldTopLine: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
SourceChangeCache: TSourceChangeCache;
Interactive: Boolean): boolean;
function AddMethods(CursorPos: TCodeXYPosition;// position in class declaration
OldTopLine: integer;
ListOfPCodeXYPosition: TFPList;
const VirtualToOverride: boolean;
out NewPos: TCodeXYPosition; out NewTopLine, BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
function AddPublishedVariable(const UpperClassName,VarName, VarType: string;
SourceChangeCache: TSourceChangeCache): boolean; override;
// graph of definitions of a unit
function GatherUnitDefinitions(out TreeOfCodeTreeNodeExt: TAVLTree;
OnlyInterface, ExceptionOnRedefinition: boolean): boolean;
function BuildUnitDefinitionGraph(
out DefinitionsTreeOfCodeTreeNodeExt: TAVLTree;
out Graph: TCodeGraph; OnlyInterface: boolean): boolean;
procedure WriteCodeGraphDebugReport(Graph: TCodeGraph);
// redefinitions
function GetRedefinitionNodeText(Node: TCodeTreeNode): string;
function FindRedefinitions(out TreeOfCodeTreeNodeExt: TAVLTree;
WithEnums: boolean): boolean;
function RemoveRedefinitions(TreeOfCodeTreeNodeExt: TAVLTree;
SourceChangeCache: TSourceChangeCache): boolean;
function FindAliasDefinitions(out TreeOfCodeTreeNodeExt: TAVLTree;
OnlyWrongType: boolean): boolean;
function FixAliasDefinitions(TreeOfCodeTreeNodeExt: TAVLTree;
SourceChangeCache: TSourceChangeCache): boolean;
// const functions
function FindConstFunctions(out TreeOfCodeTreeNodeExt: TAVLTree): boolean;
function ReplaceConstFunctions(TreeOfCodeTreeNodeExt: TAVLTree;
SourceChangeCache: TSourceChangeCache): boolean;
function FindTypeCastFunctions(out TreeOfCodeTreeNodeExt: TAVLTree): boolean;
// typecast functions
function ReplaceTypeCastFunctions(TreeOfCodeTreeNodeExt: TAVLTree;
SourceChangeCache: TSourceChangeCache): boolean;
function MovePointerTypesToTargetSections(
SourceChangeCache: TSourceChangeCache): boolean;
// sort procs
function FixForwardDefinitions(SourceChangeCache: TSourceChangeCache
): boolean;
// empty functions
function FindEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string; // can be ''
const Sections: TPascalClassSections;
ListOfPCodeXYPosition: TFPList;
out AllEmpty: boolean): boolean;
function FindEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string; // can be ''
const Sections: TPascalClassSections;
CodeTreeNodeExtensions: TAVLTree;
out AllEmpty: boolean): boolean;
function RemoveEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string;
const Sections: TPascalClassSections;
SourceChangeCache: TSourceChangeCache;
out AllRemoved: boolean;
const Attr: TProcHeadAttributes;
out RemovedProcHeads: TStrings): boolean;
// assign records/classes
function FindAssignMethod(CursorPos: TCodeXYPosition;
out ClassNode: TCodeTreeNode;
out AssignDeclNode: TCodeTreeNode;
var MemberNodeExts: TAVLTree; // tree of TCodeTreeNodeExtension, Node=var or property, Data=write property
out AssignBodyNode: TCodeTreeNode;
out InheritedDeclContext: TFindContext;
ProcName: string = '' // default is 'Assign'
): boolean;
function AddAssignMethod(ClassNode: TCodeTreeNode; MemberNodeExts: TFPList;
const ProcName, ParamName, ParamType: string;
OverrideMod, CallInherited, CallInheritedOnlyInElse: boolean;
SourceChanger: TSourceChangeCache;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
LocalVarName: string = '' // default is 'aSource'
): boolean;
function AddAssignMethod(ClassNode: TCodeTreeNode; MemberNodeExts: TFPList;
const ProcName, ParamName, ParamType: string;
OverrideMod, CallInherited, CallInheritedOnlyInElse: boolean;
SourceChanger: TSourceChangeCache;
out NewPos: TCodeXYPosition; out NewTopLine, BlockTopLine, BlockBottomLine: integer;
LocalVarName: string = '' // default is 'aSource'
): boolean;
// local variables
function GetPossibleInitsForVariable(CursorPos: TCodeXYPosition;
out Statements: TStrings;
out InsertPositions: TObjectList; // list of TInsertStatementPosDescription
SourceChangeCache: TSourceChangeCache = nil // needed for Beautifier
): boolean;
// guess type of an undeclared identifier
function GuessTypeOfIdentifier(CursorPos: TCodeXYPosition;
out IsKeyword, IsSubIdentifier: boolean;
out ExistingDefinition: TFindContext; // if it already exists
out ListOfPFindContext: TFPList; // possible classes for adding as sub identifier
out NewExprType: TExpressionType; out NewType: string): boolean; // false = not at an identifier
function DeclareVariableNearBy(InsertPos: TCodeXYPosition;
const VariableName, NewType, NewUnitName: string;
Visibility: TCodeTreeNodeDesc;
SourceChangeCache: TSourceChangeCache;
LevelPos: TCodeXYPosition // optional
): boolean;
function DeclareVariableAt(CursorPos: TCodeXYPosition;
const VariableName, NewType, NewUnitName: string;
SourceChangeCache: TSourceChangeCache): boolean;
// custom class completion
function InitClassCompletion(const AClassName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function InitClassCompletion(ClassNode: TCodeTreeNode;
SourceChangeCache: TSourceChangeCache): boolean;
function ApplyClassCompletion(AddMissingProcBodies: boolean): boolean;
function ProcExistsInCodeCompleteClass(
const NameAndParamsUpCase: string; SearchInAncestors: boolean = true): boolean;
function FindProcInCodeCompleteClass(const NameAndParamsUpCase: string;
SearchInAncestors: boolean = true): TFindContext;
function VarExistsInCodeCompleteClass(const UpperName: string): boolean;
procedure AddClassInsertion(
const CleanDef, Def, IdentifierName: string;
TheType: TNewClassPart; PosNode: TCodeTreeNode = nil;
const Body: string = '');
procedure AddNeededUnitsToMainUsesSectionForRange(
StartPos, EndPos: integer; CompletionTool: TCodeCompletionCodeTool);
public
// Options; ToDo: move to options
property SetPropertyVariablename: string read FSetPropertyVariablename
write SetSetPropertyVariablename;
property SetPropertyVariableIsPrefix: Boolean
read FSetPropertyVariableIsPrefix write SetSetPropertyVariableIsPrefix;
property SetPropertyVariableUseConst: Boolean
read FSetPropertyVariableUseConst write SetSetPropertyVariableUseConst;
property CompleteProperties: boolean read FCompleteProperties
write FCompleteProperties;
property AddInheritedCodeToOverrideMethod: boolean
read FAddInheritedCodeToOverrideMethod
write FAddInheritedCodeToOverrideMethod;
property CodeCompleteClassNode: TCodeTreeNode
read FCodeCompleteClassNode write SetCodeCompleteClassNode;
property CodeCompleteSrcChgCache: TSourceChangeCache
read FSourceChangeCache write SetCodeCompleteSrcChgCache;
procedure CalcMemSize(Stats: TCTMemStats); override;
end;
type
TShowCodeCreationDlgFunc = function(const ANewIdent: string; const AIsMethod: Boolean;
out Options: TCodeCreationDlgResult): Boolean; //in case of imsPrompt show a dialog and return a "normal" section; returns true if OK, false if canceled
var
ShowCodeCreationDlg: TShowCodeCreationDlgFunc = nil;
implementation
type
TNodeMoveEdge = class
public
GraphNode: TCodeGraphNode;
DestPos: integer;
TologicalLevel: integer;
SrcPos: integer;
end;
function CompareNodeMoveEdges(NodeMove1, NodeMove2: Pointer): integer;
var
Node1: TNodeMoveEdge;
Node2: TNodeMoveEdge;
begin
Node1:=TNodeMoveEdge(NodeMove1);
Node2:=TNodeMoveEdge(NodeMove2);
if Node1.DestPos>Node2.DestPos then
Result:=1
else if Node1.DestPos<Node2.DestPos then
Result:=-1
else if Node1.TologicalLevel>Node2.TologicalLevel then
Result:=1
else if Node1.TologicalLevel<Node2.TologicalLevel then
Result:=-1
else if Node1.SrcPos>Node2.SrcPos then
Result:=1
else if Node1.SrcPos<Node2.SrcPos then
Result:=-1
else
Result:=0;
end;
{ TCodeCompletionCodeTool }
function TCodeCompletionCodeTool.ProcExistsInCodeCompleteClass(
const NameAndParamsUpCase: string; SearchInAncestors: boolean): boolean;
begin
Result:=FindProcInCodeCompleteClass(NameAndParamsUpCase,SearchInAncestors).Node<>nil;
end;
function TCodeCompletionCodeTool.FindProcInCodeCompleteClass(
const NameAndParamsUpCase: string; SearchInAncestors: boolean): TFindContext;
// NameAndParams should be uppercase and contains the proc name and the
// parameter list without names and default values
// and should not contain any comments and no result type
// e.g. DOIT(LONGINT;STRING)
var
ANodeExt: TCodeTreeNodeExtension;
Params: TFindDeclarationParams;
ClassNode, StartNode: TCodeTreeNode;
Tool: TFindDeclarationTool;
Vis: TClassSectionVisibility;
begin
Result:=CleanFindContext;
// search in new nodes, which will be inserted
ANodeExt:=FirstInsert;
while ANodeExt<>nil do begin
if CompareTextIgnoringSpace(ANodeExt.Txt,NameAndParamsUpCase,true)=0 then
begin
Result.Tool:=Self;
Result.Node:=CodeCompleteClassNode;
exit;
end;
ANodeExt:=ANodeExt.Next;
end;
// search in current class
Result.Node:=FindProcNode(FCompletingFirstEntryNode,NameAndParamsUpCase,mgMethod,
[phpInUpperCase]);
if Result.Node<>nil then begin
Result.Tool:=Self;
exit;
end;
if not SearchInAncestors then exit;
//search in ancestor classes
Params:=TFindDeclarationParams.Create;
try
ClassNode:=CodeCompleteClassNode;
Tool:=Self;
while Tool.FindAncestorOfClass(ClassNode,Params,True) do
begin
Tool:=Params.NewCodeTool;
ClassNode:=Params.NewNode;
StartNode:=GetFirstClassIdentifier(ClassNode);
if Tool=Self then
Vis := csvPrivateAndHigher
else
Vis := csvProtectedAndHigher;
Result.Node := Tool.FindProcNode(StartNode,NameAndParamsUpCase,
mgMethod,[phpInUpperCase], Vis);
if Result.Node<>nil then begin
Result.Tool:=Tool;
exit;
end;
end;
finally
Params.Free;
end;
end;
procedure TCodeCompletionCodeTool.SetCodeCompleteClassNode(const AClassNode: TCodeTreeNode);
begin
FreeClassInsertionList;
FJumpToProcHead.Name:='';
FCodeCompleteClassNode:=AClassNode;
if CodeCompleteClassNode=nil then begin
FCompletingFirstEntryNode:=nil;
exit;
end;
ClearIgnoreErrorAfter;
// find first variable/method/GUID
FCompletingFirstEntryNode:=GetFirstClassIdentifier(CodeCompleteClassNode);
end;
procedure TCodeCompletionCodeTool.SetCodeCompleteSrcChgCache(
const AValue: TSourceChangeCache);
begin
FSourceChangeCache:=AValue;
FSourceChangeCache.MainScanner:=Scanner;
end;
procedure TCodeCompletionCodeTool.SetSetPropertyVariableIsPrefix(aValue: Boolean
);
begin
if FSetPropertyVariableIsPrefix = aValue then Exit;
FSetPropertyVariableIsPrefix := aValue;
end;
procedure TCodeCompletionCodeTool.SetSetPropertyVariablename(AValue: string);
begin
if FSetPropertyVariablename=aValue then Exit;
FSetPropertyVariablename:=aValue;
end;
procedure TCodeCompletionCodeTool.SetSetPropertyVariableUseConst(aValue: Boolean
);
begin
if FSetPropertyVariableUseConst = aValue then Exit;
FSetPropertyVariableUseConst := aValue;
end;
function TCodeCompletionCodeTool.OnTopLvlIdentifierFound(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
var
TrimmedIdentifier: string;
begin
if not (fdfTopLvlResolving in Params.Flags) then exit(ifrProceedSearch);
with FoundContext do begin
case Node.Desc of
ctnTypeDefinition,ctnVarDefinition,ctnConstDefinition,ctnGenericType:
TrimmedIdentifier:=Tool.ExtractDefinitionName(Node);
ctnProperty:
TrimmedIdentifier:=Tool.ExtractPropName(Node,false);
else
TrimmedIdentifier:=GetIdentifier(Params.Identifier);
end;
end;
fFullTopLvlName:=fFullTopLvlName+TrimmedIdentifier;
Result:=ifrSuccess;
end;
procedure TCodeCompletionCodeTool.RemoveNewMainUsesSectionUnit(p: PChar);
var
AVLNode: TAVLTreeNode;
s: string;
begin
if fNewMainUsesSectionUnits=nil then exit;
AVLNode:=fNewMainUsesSectionUnits.Find(p);
if AVLNode=nil then exit;
Pointer(s):=AVLNode.Data;
s:='';
fNewMainUsesSectionUnits.Delete(AVLNode);
if s='' then ;
end;
procedure TCodeCompletionCodeTool.CheckWholeUnitParsed(var Node1,
Node2: TCodeTreeNode; Range: TLinkScannerRange);
var
Pos1: Integer;
Pos2: Integer;
begin
//DebugLn(['TCodeCompletionCodeTool.CheckWholeUnitParsed ',EndOfSourceFound,' LastErrorMessage="',LastErrorMessage,'" LastErrorCurPos=',dbgs(LastErrorCurPos)]);
if (ScannedRange>=Range) and (not LastErrorValid) then exit;
Pos1:=0;
Pos2:=0;
if Node1<>nil then Pos1:=Node1.StartPos;
if Node2<>nil then Pos2:=Node2.StartPos;
ClearIgnoreErrorAfter;
BuildTree(Range);
if Node1<>nil then Node1:=FindDeepestNodeAtPos(Pos1,true);
if Node2<>nil then Node2:=FindDeepestNodeAtPos(Pos2,true);
end;
function TCodeCompletionCodeTool.VarExistsInCodeCompleteClass(
const UpperName: string): boolean;
var
ANodeExt: TCodeTreeNodeExtension;
Params: TFindDeclarationParams;
ClassNode, CompletingChildNode: TCodeTreeNode;
Tool: TFindDeclarationTool;
Vis: TClassSectionVisibility;
begin
Result:=false;
// search in new nodes, which will be inserted
ANodeExt:=FirstInsert;
while ANodeExt<>nil do begin
if CompareTextIgnoringSpace(ANodeExt.Txt,UpperName,true)=0 then
exit(true);
ANodeExt:=ANodeExt.Next;
end;
// search in current class
Result:=(FindVarNode(FCompletingFirstEntryNode,UpperName)<>nil);
if not Result then
begin
//search in ancestor classes
Params:=TFindDeclarationParams.Create;
try
ClassNode:=CodeCompleteClassNode;
Tool:=Self;
while not Result and Tool.FindAncestorOfClass(ClassNode,Params,True) do begin
Tool:=Params.NewCodeTool;
ClassNode:=Params.NewNode;
CompletingChildNode:=GetFirstClassIdentifier(ClassNode);
if Tool=Self then
Vis := csvPrivateAndHigher
else
Vis := csvProtectedAndHigher;
Result := (Tool.FindVarNode(CompletingChildNode,UpperName,Vis)<>nil);
end;
finally
Params.Free;
end;
end;
end;
procedure TCodeCompletionCodeTool.AddClassInsertion(
const CleanDef, Def, IdentifierName: string; TheType: TNewClassPart;
PosNode: TCodeTreeNode; const Body: string);
{ add an insert request entry to the list of insertions
For example: a request to insert a new variable or a new method to the class
CleanDef: The skeleton of the new insertion. e.g. the variablename or the
method header without parameter names.
Def: The insertion code.
IdentifierName: e.g. the variablename or the method name
TheType: see TNewClassPart
PosNode: optional. The node, to which the request belongs. e.g. the
property node, if the insert is the auto created private variable.
Body: optional. Normally a method body is auto created. This overrides
the body code.
}
var NewInsert, InsertPos, LastInsertPos: TCodeTreeNodeExtension;
Beauty: TBeautifyCodeOptions;
begin
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.AddClassInsertion] CleanDef="',CleanDef,'" Def="',Def,'" Identifiername="',Identifiername,'" Body="',Body,'"');
{$ENDIF}
if CodeCompleteClassNode.Desc in AllClassInterfaces then begin
// a class interface has no section -> put them all into 'public'
if TheType in NewClassPartProcs then
TheType:=ncpPublicProcs
else if TheType in NewClassPartVars then
raise Exception.Create('TCodeCompletionCodeTool.AddClassInsertion can not add variables to a class interface');
end;
NewInsert:=TCodeTreeNodeExtension.Create;
with NewInsert do begin
Node:=PosNode;
Txt:=CleanDef;
ExtTxt1:=Def;
ExtTxt2:=IdentifierName;
ExtTxt3:=Body;
Flags:=ord(TheType);
end;
if FirstInsert=nil then begin
FirstInsert:=NewInsert;
exit;
end;
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
if Beauty.ClassPartInsertPolicy=cpipLast then
begin
// add as last to inserts
InsertPos:=FirstInsert;
while (InsertPos.Next<>nil) do
InsertPos:=InsertPos.Next;
InsertPos.Next:=NewInsert;
end else begin
// insert alphabetically
InsertPos:=FirstInsert;
LastInsertPos:=nil;
//DebugLn('GGG "',InsertPos.Txt,'" "',CleanDef,'" ',CompareTextIgnoringSpace(InsertPos.Txt,CleanDef,false));
while (InsertPos<>nil)
and (CompareTextIgnoringSpace(InsertPos.Txt,CleanDef,false)>=0) do begin
LastInsertPos:=InsertPos;
InsertPos:=InsertPos.Next;
end;
if LastInsertPos<>nil then begin
// insert after LastInsertPos
NewInsert.Next:=LastInsertPos.Next;
LastInsertPos.Next:=NewInsert;
end else begin
// insert as first
NewInsert.Next:=InsertPos;
FirstInsert:=NewInsert;
end;
{InsertPos:=FirstInsert;
while InsertPos<>nil do begin
DebugLn(' HHH ',InsertPos.Txt);
InsertPos:=InsertPos.Next;
end;}
end;
end;
procedure TCodeCompletionCodeTool.FreeClassInsertionList;
// dispose all new variables/procs definitions
var
ANodeExt: TCodeTreeNodeExtension;
AVLNode: TAVLTreeNode;
s: string;
begin
while FirstInsert<>nil do begin
ANodeExt:=FirstInsert;
FirstInsert:=FirstInsert.Next;
ANodeExt.Free;
end;
if fNewMainUsesSectionUnits<>nil then begin
AVLNode:=fNewMainUsesSectionUnits.FindLowest;
while AVLNode<>nil do begin
Pointer(s):=AVLNode.Data;
s:='';
AVLNode:=fNewMainUsesSectionUnits.FindSuccessor(AVLNode);
end;
if s='' then ;
FreeAndNil(fNewMainUsesSectionUnits);
end;
end;
function TCodeCompletionCodeTool.NodeExtIsVariable(
ANodeExt: TCodeTreeNodeExtension): boolean;
begin
Result:=TNewClassPart(ANodeExt.Flags) in NewClassPartVars;
end;
function TCodeCompletionCodeTool.NodeExtHasVisibilty(
ANodeExt: TCodeTreeNodeExtension; Visibility: TPascalClassSection): boolean;
begin
case Visibility of
pcsPrivate:
Result:=(ANodeExt.Flags=ord(ncpPrivateVars))
or (ANodeExt.Flags=ord(ncpPrivateProcs));
pcsProtected:
Result:=(ANodeExt.Flags=ord(ncpProtectedVars))
or (ANodeExt.Flags=ord(ncpProtectedProcs));
pcsPublic:
Result:=(ANodeExt.Flags=ord(ncpPublicVars))
or (ANodeExt.Flags=ord(ncpPublicProcs));
pcsPublished:
Result:=(ANodeExt.Flags=ord(ncpPublishedVars))
or (ANodeExt.Flags=ord(ncpPublishedProcs));
else
Result:=false;
end;
end;
procedure TCodeCompletionCodeTool.FindInsertPositionForForwardProc(
SourceChangeCache: TSourceChangeCache; ProcNode: TCodeTreeNode; out Indent,
InsertPos: integer);
var
Beauty: TBeautifyCodeOptions;
procedure SetIndentAndInsertPos(Node: TCodeTreeNode; Behind: boolean);
begin
Indent:=Beauty.GetLineIndent(Src,Node.StartPos);
if Behind then
InsertPos:=FindLineEndOrCodeAfterPosition(Node.EndPos)
else
InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
end;
var
NearestProcNode, StartSearchProc: TCodeTreeNode;
IsInInterface: boolean;
ProcBodyNodes, ForwardProcNodes: TAVLTree; // tree of TCodeTreeNodeExtension
ProcAVLNode, NearestAVLNode: TAVLTreeNode;
ProcNodeExt, NearestNodeExt: TCodeTreeNodeExtension;
InsertBehind: boolean;
NearestAVLNodeInFront: TAVLTreeNode;
NearestAVLNodeBehind: TAVLTreeNode;
ProcPosInFront: Integer;
ProcPosBehind: Integer;
EmptyLinesInFront: Integer;
EmptyLinesBehind: Integer;
begin
Indent:=0;
InsertPos:=0;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
IsInInterface:=ProcNode.HasParentOfType(ctnInterface);
if IsInInterface then begin
// forward proc in interface
StartSearchProc:=FindImplementationNode;
if StartSearchProc=nil then
RaiseException(20170421201438,'Implementation section not found');
if StartSearchProc.FirstChild<>nil then begin
// implementation not empty
StartSearchProc:=StartSearchProc.FirstChild
end else begin
// implementation is empty
// -> add it as first body
Indent:=Beauty.GetLineIndent(Src,StartSearchProc.StartPos);
InsertPos:=StartSearchProc.StartPos+length('implementation');
exit;
end;
end else begin
// forward proc in code
// start searching for bodies behind proc
StartSearchProc:=ProcNode.NextBrother;
if StartSearchProc=nil then begin
// There are no nodes behind
// -> insert code directly behind
SetIndentAndInsertPos(ProcNode,true);
exit;
end;
end;
//debugln(['TCodeCompletionCodeTool.FindInsertPositionForForwardProc ',ord(Beauty.ForwardProcBodyInsertPolicy)]);
if Beauty.KeepForwardProcOrder then begin
// KeepForwardProcOrder: gather all procs and try to insert the new body
// in the same order of other forward proc definitions.
ForwardProcNodes:=nil;
ProcAVLNode:=nil;
ProcBodyNodes:=nil;
ProcNodeExt:=nil;
try
// gather all forward procs definitions on the same level
ForwardProcNodes:=GatherProcNodes(ProcNode.Parent.FirstChild,
[phpInUpperCase,phpIgnoreProcsWithBody,phpIgnoreMethods],'');
// gather all proc bodies
ProcBodyNodes:=GatherProcNodes(StartSearchProc,
[phpInUpperCase,phpIgnoreForwards,phpIgnoreMethods],'');
// remove current forward proc from tree
ProcAVLNode:=FindAVLNodeWithNode(ForwardProcNodes,ProcNode);
if ProcAVLNode=nil then
RaiseException(20170421201441,'TCodeCompletionCodeTool.FindInsertPositionForForwardProc '
+' Internal Error, current forward proc not found');
ProcNodeExt:=TCodeTreeNodeExtension(ProcAVLNode.Data);
ForwardProcNodes.Delete(ProcAVLNode);
// remove all forward procs without bodies
IntersectProcNodes(ForwardProcNodes,ProcBodyNodes,true);
// sort forward proc definitions with source position
ForwardProcNodes.OnCompare:=@CompareCodeTreeNodeExtWithNodeStartPos;
// For debugging:
{ProcAVLNode:=ForwardProcNodes.FindLowest;
while ProcAVLNode<>nil do begin
NearestProcNode:=TCodeTreeNodeExtension(ProcAVLNode.Data).Node;
DebugLn(['FindInsertPositionForForwardProc B ',NearestProcNode.StartPos,' "',copy(Src,NearestProcNode.StartPos,20),'"']);
ProcAVLNode:=ForwardProcNodes.FindSuccessor(ProcAVLNode);
end;}
// find nearest forward procs (distance measured in chars)
NearestAVLNode:=ForwardProcNodes.FindNearest(ProcNodeExt);
if NearestAVLNode<>nil then begin
//DebugLn('FindInsertPositionForForwardProc Nearest ',TCodeTreeNodeExtension(NearestAVLNode.Data).Node.StartPos,' ',ProcNode.StartPos);
// find nearest forward procs in front and after
if TCodeTreeNodeExtension(NearestAVLNode.Data).Node.StartPos
<ProcNode.StartPos
then begin
NearestAVLNodeInFront:=NearestAVLNode;
NearestAVLNodeBehind:=ForwardProcNodes.FindPrecessor(NearestAVLNode);
end else begin
NearestAVLNodeInFront:=ForwardProcNodes.FindSuccessor(NearestAVLNode);
NearestAVLNodeBehind:=NearestAVLNode;
end;
// choose the nearest of both (distance measured in empty lines,
// this way blocks of procs are kept)
if (NearestAVLNodeInFront<>nil) and (NearestAVLNodeBehind<>nil) then
begin
ProcPosInFront:=
TCodeTreeNodeExtension(NearestAVLNodeInFront.Data).Node.StartPos;
ProcPosBehind:=
TCodeTreeNodeExtension(NearestAVLNodeBehind.Data).Node.StartPos;
EmptyLinesInFront:=EmptyCodeLineCount(Src,
ProcPosInFront,ProcNode.StartPos,Scanner.NestedComments);
EmptyLinesBehind:=EmptyCodeLineCount(Src,
ProcNode.StartPos,ProcPosBehind,Scanner.NestedComments);
//DebugLn('FindInsertPositionForForwardProc Nearest InFront or After: EmptyLinesInFront=',EmptyLinesInFront,' EmptyLinesBehind=',EmptyLinesBehind);
if EmptyLinesInFront<EmptyLinesBehind then
NearestAVLNode:=NearestAVLNodeInFront
else
NearestAVLNode:=NearestAVLNodeBehind;
end;
NearestNodeExt:=TCodeTreeNodeExtension(NearestAVLNode.Data);
NearestProcNode:=NearestNodeExt.Node;
//DebugLn('FindInsertPositionForForwardProc C ',NearestProcNode.StartPos,' "',copy(Src,NearestProcNode.StartPos,20),'"');
InsertBehind:=NearestProcNode.StartPos<ProcNode.StartPos;
// the corresponding body was linked by IntersectProcNodes in Data
NearestAVLNode:=TAVLTreeNode(NearestNodeExt.Data);
NearestNodeExt:=TCodeTreeNodeExtension(NearestAVLNode.Data);
NearestProcNode:=NearestNodeExt.Node;
SetIndentAndInsertPos(NearestProcNode,InsertBehind);
exit;
end else begin
// there is no other proc => use ForwardProcBodyInsertPolicy
end;
finally
// clean up
ProcNodeExt.Free;
DisposeAVLTree(ProcBodyNodes);
DisposeAVLTree(ForwardProcNodes);
end;
end;
if Beauty.ForwardProcBodyInsertPolicy = fpipInFrontOfMethods
then begin
// Try to insert new proc in front of existing methods
// find first method
NearestProcNode:=StartSearchProc;
while (NearestProcNode<>nil) and (not NodeIsMethodBody(NearestProcNode)) do
NearestProcNode:=NearestProcNode.NextBrother;
if NearestProcNode<>nil then begin
// the comments in front of the first method probably belong to the class
// Therefore insert behind the node in front of the first method
if NearestProcNode.PriorBrother<>nil then
SetIndentAndInsertPos(NearestProcNode.PriorBrother,true)
else begin
Indent:=Beauty.GetLineIndent(Src,NearestProcNode.StartPos);
InsertPos:=NearestProcNode.Parent.StartPos;
while (InsertPos<=NearestProcNode.StartPos)
and (not IsSpaceChar[Src[InsertPos]]) do
inc(InsertPos);
end;
exit;
end;
end else if Beauty.ForwardProcBodyInsertPolicy = fpipBehindMethods
then begin
// Try to insert new proc behind existing methods
// find last method (go to last brother and search backwards)
NearestProcNode:=StartSearchProc;
while (NearestProcNode.NextBrother<>nil) do
NearestProcNode:=NearestProcNode.NextBrother;
while (NearestProcNode<>nil) and (not NodeIsMethodBody(NearestProcNode)) do
NearestProcNode:=NearestProcNode.PriorBrother;
if NearestProcNode<>nil then begin
SetIndentAndInsertPos(NearestProcNode,true);
exit;
end;
end;
// Default position: Insert behind last node
NearestProcNode:=StartSearchProc;
while (NearestProcNode.NextBrother<>nil) do
NearestProcNode:=NearestProcNode.NextBrother;
if NearestProcNode<>nil then begin
SetIndentAndInsertPos(NearestProcNode,true);
exit;
end;
RaiseException(20170421201444,'TCodeCompletionCodeTool.FindInsertPositionForForwardProc '
+' Internal Error: no insert position found');
end;
procedure TCodeCompletionCodeTool.FindInsertPositionForProcInterface(
var Indent, InsertPos: integer; SourceChangeCache: TSourceChangeCache);
var
InsertNode: TCodeTreeNode;
Beauty: TBeautifyCodeOptions;
begin
Beauty:=SourceChangeCache.BeautifyCodeOptions;
InsertNode:=FindInterfaceNode;
if InsertNode<>nil then begin
// there is an interface
// -> append at end of interface
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertNode.EndPos,true);
Indent:=Beauty.GetLineIndent(Src,InsertNode.EndPos);
end;
if InsertPos<1 then begin
// there is no interface
// -> insert in front of any proc
InsertNode:=FindFirstSectionChild;
while (InsertNode<>nil) and (InsertNode.Desc<>ctnProcedure) do
InsertNode:=InsertNode.NextBrother;
if InsertNode<>nil then begin
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertNode.StartPos,true);
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end;
end;
if InsertPos<1 then begin
InsertNode:=FindFirstSectionChild;
if (InsertNode<>nil) and (InsertNode.Desc=ctnSrcName) then
InsertNode:=InsertNode.NextBrother;
if InsertNode<>nil then begin
Indent:=Beauty.GetLineIndent(Src,InsertNode.StartPos);
if InsertNode.Desc=ctnUsesSection then
// insert behind uses section
InsertPos:=FindLineEndOrCodeAfterPosition(InsertNode.EndPos)
else
// insert as first
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertNode.StartPos);
end else begin
// insert in interface or somewhere at start
InsertNode:=Tree.Root;
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertNode.EndPos,true);
Indent:=Beauty.GetLineIndent(Src,InsertNode.EndPos);
end;
end;
end;
function TCodeCompletionCodeTool.FindProcAndClassNode(CursorNode: TCodeTreeNode;
out ProcNode, AClassNode: TCodeTreeNode): boolean;
var
ANode: TCodeTreeNode;
SearchedClassName: string;
begin
Result:=false;
AClassNode:=nil;
ProcNode:=CursorNode;
while (ProcNode<>nil) do begin
if (ProcNode.Desc=ctnProcedure) then begin
SearchedClassname:=ExtractClassNameOfProcNode(ProcNode,true);
if SearchedClassName<>'' then break;
end;
ProcNode:=ProcNode.Parent;
end;
if (ProcNode=nil) then exit;
ANode:=FindClassNodeForMethodBody(ProcNode,true,false);
if (ANode=nil) then exit;
// search class node
while ANode<>nil do begin
if ANode.Desc in AllClassObjects then break;
ANode:=ANode.Parent;
end;
if ANode=nil then exit;
AClassNode:=ANode;
Result:=true;
end;
function TCodeCompletionCodeTool.CheckLocalVarAssignmentSyntax(
CleanCursorPos: integer; out VarNameAtom, AssignmentOperator,
TermAtom: TAtomPosition): boolean;
// check for VarName:=Term
begin
Result:=false;
MoveCursorToCleanPos(CleanCursorPos);
// find variable name
GetIdentStartEndAtPosition(Src,CleanCursorPos,
VarNameAtom.StartPos,VarNameAtom.EndPos);
//debugln('TCodeCompletionCodeTool.CheckLocalVarAssignmentSyntax VarNameAtom="',dbgstr(Src,VarNameAtom.StartPos,VarNameAtom.EndPos-VarNameAtom.StartPos),'"');
if VarNameAtom.StartPos=VarNameAtom.EndPos then exit;
MoveCursorToAtomPos(VarNameAtom);
if AtomIsKeyWord then exit;
// find assignment operator
ReadNextAtom;
if not (AtomIs(':=') or AtomIs('+=') or AtomIs('-=') or AtomIs('*=')
or AtomIs('/=')) then exit;
AssignmentOperator:=CurPos;
//debugln('TCodeCompletionCodeTool.CheckLocalVarAssignmentSyntax AssignmentOperator="',dbgstr(Src,AssignmentOperator.StartPos,AssignmentOperator.EndPos-AssignmentOperator.StartPos),'"');
// find term
ReadNextAtom;
TermAtom.StartPos:=CurPos.StartPos;
TermAtom.EndPos:=FindEndOfExpression(TermAtom.StartPos);
//debugln('TCodeCompletionCodeTool.CheckLocalVarAssignmentSyntax TermAtom="',dbgstr(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos),'"');
Result:=TermAtom.EndPos>TermAtom.StartPos;
end;
function TCodeCompletionCodeTool.CheckLocalVarForInSyntax(
CleanCursorPos: integer; out VarNameAtom, TermAtom: TAtomPosition): boolean;
// check for: for VarName in Term do
{off $DEFINE VerboseForInCompletion}
var
InAtomEndPos: LongInt;
begin
Result:=false;
MoveCursorToCleanPos(CleanCursorPos);
// find variable name
GetIdentStartEndAtPosition(Src,CleanCursorPos,
VarNameAtom.StartPos,VarNameAtom.EndPos);
//debugln('TCodeCompletionCodeTool.CheckLocalVarForInSyntax A ',GetAtom(VarNameAtom),' "',copy(Src,CleanCursorPos,10),'"');
if VarNameAtom.StartPos=VarNameAtom.EndPos then begin
{$IFDEF VerboseForInCompletion}
debugln('TCodeCompletionCodeTool.CheckLocalVarForInSyntax no identifier at cursor ',GetAtom(VarNameAtom),' "',copy(Src,CleanCursorPos,10),'"');
{$ENDIF}
exit;
end;
MoveCursorToAtomPos(VarNameAtom);
if AtomIsKeyWord then exit;
// find 'in' operator
ReadNextAtom;
if not UpAtomIs('IN') then begin
{$IFDEF VerboseForInCompletion}
debugln('TCodeCompletionCodeTool.CheckLocalVarForInSyntax no in keyword ',GetAtom(VarNameAtom));
{$ENDIF}
exit;
end;
InAtomEndPos:=CurPos.EndPos;
// find 'for' keyword
MoveCursorToCleanPos(VarNameAtom.StartPos);
ReadPriorAtom;
if not UpAtomIs('FOR') then begin
{$IFDEF VerboseForInCompletion}
debugln('TCodeCompletionCodeTool.CheckLocalVarForInSyntax no for keyword ',GetAtom);
{$ENDIF}
exit;
end;
// find term
MoveCursorToCleanPos(InAtomEndPos);
ReadNextAtom;
TermAtom.StartPos:=CurPos.StartPos;
TermAtom.EndPos:=FindEndOfExpression(TermAtom.StartPos);
{$IFDEF VerboseForInCompletion}
debugln('TCodeCompletionCodeTool.CheckLocalVarForInSyntax term="',GetAtom(TermAtom),'"');
{$ENDIF}
Result:=TermAtom.EndPos>TermAtom.StartPos;
end;
function TCodeCompletionCodeTool.AddLocalVariable(CleanCursorPos: integer;
OldTopLine: integer; VariableName, VariableType,
VariableTypeUnitName: string; out NewPos: TCodeXYPosition;
out NewTopLine: integer; SourceChangeCache: TSourceChangeCache;
CleanLevelPos: integer): boolean;
// if CleanLevelPos<1 then CleanLevelPos:=CleanCursorPos
// CleanLevelPos selects the target node, e.g. a ctnProcedure
function FindFirstVarDeclaration(var Node: TCodeTreeNode): TCodeTreeNode;
begin
Result := Node;
while Assigned(Result.PriorBrother) and (Result.PriorBrother.Desc = ctnVarDefinition) and
not Assigned(Result.PriorBrother.LastChild)
do
Result := Result.PriorBrother;
end;
var
CursorNode, VarSectionNode, VarNode: TCodeTreeNode;
Indent, InsertPos: integer;
InsertTxt: string;
OldCodePos: TCodePosition;
Node: TCodeTreeNode;
ParentNode: TCodeTreeNode;
OtherSectionNode: TCodeTreeNode;
HeaderNode: TCodeTreeNode;
Beauty: TBeautifyCodeOptions;
VarTypeNode: TCodeTreeNode;
InsertVarLineStart: integer;
InsertVarLineEnd: integer;
InsertAsNewLine: Boolean;
begin
Result:=false;
if CleanLevelPos<1 then CleanLevelPos:=CleanCursorPos;
//DebugLn('TCodeCompletionCodeTool.AddLocalVariable START CleanCursorPos=',CleanPosToStr(CleanCursorPos),' CleanLevelPos=',CleanPosToStr(CleanLevelPos));
if not CleanPosToCodePos(CleanCursorPos,OldCodePos) then begin
RaiseException(20170421201447,'TCodeCompletionCodeTool.AddLocalVariable Internal Error: '
+'CleanPosToCodePos');
end;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// find the level and find sections in front
Node:=Tree.Root;
CursorNode:=nil;
VarSectionNode:=nil;
OtherSectionNode:=nil;
HeaderNode:=nil;
ParentNode:=nil;
while Node<>nil do begin
if Node.StartPos>CleanCursorPos then break;
CursorNode:=Node;
if Node.Desc in [ctnProcedureHead,ctnUsesSection] then
HeaderNode:=Node
else if Node.Desc=ctnVarSection then
VarSectionNode:=Node
else if Node.Desc in AllDefinitionSections then
OtherSectionNode:=Node;
if (Node.StartPos<=CleanLevelPos)
and ((Node.EndPos>CleanLevelPos)
or ((Node.EndPos=CleanLevelPos)
and ((Node.NextBrother=nil) or (Node.NextBrother.StartPos>CleanLevelPos))))
then begin
if Node.Desc in [ctnInterface,ctnImplementation,ctnProgram,ctnLibrary,
ctnPackage,ctnProcedure]
then begin
// this node can have a var section
VarSectionNode:=nil;
OtherSectionNode:=nil;
HeaderNode:=nil;
ParentNode:=Node;
end else if Node.Desc=ctnUnit then begin
// the grand children can have a var section
end else begin
break;
end;
Node:=Node.FirstChild;
end else
Node:=Node.NextBrother;
end;
if ParentNode=nil then begin
// no target for a var
RaiseException(20170421201449,'TCodeCompletionCodeTool.AddLocalVariable Internal Error: '
+'invalid target for a var');
end;
{$IFDEF EnableCodeCompleteTemplates}
if ( CTTemplateExpander <> nil )
and CTTemplateExpander.TemplateExists('PrettyColon') then
begin
InsertTxt:=VariableName+CTTemplateExpander.Expand('PrettyColon','','',[],[])
+VariableType+';';
end
else
{$ENDIF}
begin
InsertTxt:=VariableName+':'+VariableType+';';
//DebugLn(['TCodeCompletionCodeTool.AddLocalVariable C InsertTxt="',InsertTxt,'" ParentNode=',ParentNode.DescAsString,' HeaderNode=',HeaderNode.DescAsString,' OtherSectionNode=',OtherSectionNode.DescAsString,' VarSectionNode=',VarSectionNode.DescAsString,' CursorNode=',CursorNode.DescAsString]);
end;
InsertAsNewLine := True;
if (VarSectionNode<>nil) then begin
//debugln(['TCodeCompletionCodeTool.AddLocalVariable insert into existing var section']);
// there is already a var section
// -> first check if variables with the same type are defined (search backwards)
VarTypeNode := nil;
if Beauty.GroupLocalVariables then
begin
VarNode:=VarSectionNode.LastChild;
while Assigned(VarNode) and not Assigned(VarTypeNode) do
begin
if (VarNode.Desc = ctnVarDefinition) and Assigned(VarNode.LastChild) and
(VarNode.LastChild.Desc = ctnIdentifier) and
(CompareTextIgnoringSpace(VariableType,ExtractNode(VarNode.LastChild,[phpCommentsToSpace]),False) = 0)
then
VarTypeNode := VarNode;
VarNode := VarNode.PriorBrother;
end;
end;
if Assigned(VarTypeNode) then
begin
// -> append variable to already defined line
VarNode := FindFirstVarDeclaration(VarTypeNode);//find starting indentation
Indent:=Beauty.GetLineIndent(Src,VarTypeNode.StartPos);
if PositionsInSameLine(Src,VarTypeNode.StartPos,VarNode.StartPos) then
inc(Indent,Beauty.Indent);
MoveCursorToNodeStart(VarTypeNode.LastChild);
ReadPriorAtom;
if CurPos.Flag = cafColon then
begin
InsertPos:=CurPos.StartPos;
GetLineStartEndAtPosition(Src, InsertPos, InsertVarLineStart, InsertVarLineEnd);
InsertTxt:=VariableName;
if InsertPos-InsertVarLineStart+Length(VariableName)+2 > Beauty.LineLength then//the variable name doesn't fit into the line
InsertTxt := Beauty.LineEnd + Beauty.GetIndentStr(Indent) + InsertTxt
else if InsertVarLineEnd-InsertVarLineStart+Length(VariableName)+2 > Beauty.LineLength then//the variable type doesn't fit into the line
begin
if atColon in Beauty.DoNotSplitLineInFront then
InsertTxt := Beauty.LineEnd + Beauty.GetIndentStr(Indent) + InsertTxt
else
InsertTxt := InsertTxt + Beauty.LineEnd + Beauty.GetIndentStr(Indent);
end;
InsertTxt:=','+InsertTxt;
Indent := 0;
InsertAsNewLine := False;
end else
VarTypeNode := nil;//error: colon not found, insert as new line
end;
if not Assigned(VarTypeNode) then
begin
// -> append variable to new line
VarNode:=VarSectionNode.LastChild;
if VarNode<>nil then begin
InsertPos:=FindLineEndOrCodeAfterPosition(VarNode.EndPos);
VarNode := FindFirstVarDeclaration(VarNode);//find indentation of first var definition
Indent:=Beauty.GetLineIndent(Src,VarNode.StartPos);
if PositionsInSameLine(Src,VarSectionNode.StartPos,VarNode.StartPos) then
inc(Indent,Beauty.Indent);
end else begin
Indent:=Beauty.GetLineIndent(Src,VarSectionNode.StartPos)+Beauty.Indent;
MoveCursorToNodeStart(VarSectionNode);
ReadNextAtom;
InsertPos:=CurPos.EndPos;
end;
end;
end else begin
// there is no var section yet
// -> create a new var section and append variable
if OtherSectionNode<>nil then begin
// there is a type/const section in front
// => put the var section below
//debugln(['TCodeCompletionCodeTool.AddLocalVariable start a new var section below '+OtherSectionNode.DescAsString]);
InsertPos:=OtherSectionNode.EndPos;
Indent:=Beauty.GetLineIndent(Src,OtherSectionNode.StartPos);
end else begin
// there is no var/type/const section in front
if (ParentNode.Desc=ctnProcedure) and (HeaderNode=nil) then
HeaderNode:=ParentNode.FirstChild;
if (HeaderNode=nil) then
HeaderNode:=FindUsesNode(ParentNode);
if CursorNode.Desc in [ctnBeginBlock,ctnAsmBlock] then begin
// add the var section directly in front of the begin
//debugln(['TCodeCompletionCodeTool.AddLocalVariable start a new var section in front of begin block']);
InsertPos:=CursorNode.StartPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end else if HeaderNode<>nil then begin
// put the var section below the header
//debugln(['TCodeCompletionCodeTool.AddLocalVariable start a new var section below '+HeaderNode.DescAsString]);
InsertPos:=HeaderNode.EndPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end else begin
// insert behind section keyword
//debugln(['TCodeCompletionCodeTool.AddLocalVariable start a new var section at start of '+ParentNode.DescAsString]);
MoveCursorToNodeStart(ParentNode);
ReadNextAtom;
InsertPos:=CurPos.EndPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end;
end;
InsertTxt:='var'+Beauty.LineEnd
+Beauty.GetIndentStr(Indent+Beauty.Indent)+InsertTxt;
end;
// insert new code
InsertTxt:=Beauty.BeautifyStatement(InsertTxt,Indent);
//DebugLn('TCodeCompletionCodeTool.AddLocalVariable E ',InsertTxt,' ');
if InsertAsNewLine then
SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,InsertTxt)
else
SourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertPos,InsertTxt);
if (VariableTypeUnitName<>'')
and (not IsHiddenUsedUnit(PChar(VariableTypeUnitName))) then begin
if not AddUnitToMainUsesSection(VariableTypeUnitName,'',SourceChangeCache)
then begin
debugln(['TCodeCompletionCodeTool.AddLocalVariable AddUnitToMainUsesSection failed']);
exit;
end;
end;
if not SourceChangeCache.Apply then begin
debugln(['TCodeCompletionCodeTool.AddLocalVariable SourceChangeCache.Apply failed']);
exit;
end;
// adjust cursor position
AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine);
Result:=true;
end;
procedure TCodeCompletionCodeTool.AdjustCursor(OldCodePos: TCodePosition;
OldTopLine: integer; out NewPos: TCodeXYPosition; out NewTopLine: integer);
begin
OldCodePos.Code.AdjustPosition(OldCodePos.P);
NewPos.Code:=OldCodePos.Code;
OldCodePos.Code.AbsoluteToLineCol(OldCodePos.P,NewPos.Y,NewPos.X);
NewTopLine:=NewPos.Y-VisibleEditorLines+1;
if NewTopLine<1 then NewTopLine:=1;
if NewTopLine<OldTopLine then
NewTopLine:=OldTopLine;
//DebugLn('TCodeCompletionCodeTool.AdjustCursor END NewPos: Line=',NewPos.Y,' Col=',NewPos.X,' NewTopLine=',NewTopLine);
end;
procedure TCodeCompletionCodeTool.AddNeededUnitToMainUsesSection(
AnUnitName: PChar);
var
s: String;
begin
if GetIdentLen(AnUnitName)=0 then exit;
if CompareIdentifiers(AnUnitName,'System')=0 then exit;
if (CompareIdentifiers(AnUnitName,'ObjPas')=0)
and (Scanner.CompilerMode in [cmDELPHI,cmOBJFPC])
and (Scanner.PascalCompiler=pcFPC) then
exit;
if (CompareIdentifiers(AnUnitName,'MacPas')=0)
and (Scanner.CompilerMode=cmMacPas)
and (Scanner.PascalCompiler=pcFPC) then
exit;
if fNewMainUsesSectionUnits=nil then
fNewMainUsesSectionUnits:=TAVLTree.Create(TListSortCompare(@CompareDottedIdentifiers));
//DebugLn(['TCodeCompletionCodeTool.AddNeededUnitToMainUsesSection AnUnitName="',AnUnitName,'"']);
if fNewMainUsesSectionUnits.Find(AnUnitName)<>nil then exit;
s:=StrPas(AnUnitName);
fNewMainUsesSectionUnits.Add(Pointer(s));
Pointer(s):=nil;
end;
function TCodeCompletionCodeTool.AddMethodCompatibleToProcType(
AClassNode: TCodeTreeNode; const AnEventName: string;
ProcContext: TFindContext; out MethodDefinition: string; out
MethodAttr: TProcHeadAttributes; SourceChangeCache: TSourceChangeCache;
Interactive: Boolean): Boolean;
var
CleanMethodDefinition: string;
Beauty: TBeautifyCodeOptions;
CCOptions: TCodeCreationDlgResult;
begin
Result := False;
MethodDefinition:='';
MethodAttr:=[];
{$IFDEF CTDEBUG}
DebugLn(' CompleteEventAssignment: Extract method param list...');
{$ENDIF}
// extract method param list and result type
CleanMethodDefinition:=UpperCaseStr(AnEventName)
+ProcContext.Tool.ExtractProcHead(ProcContext.Node,
[phpWithoutClassName, phpWithoutName, phpInUpperCase]);
{$IFDEF CTDEBUG}
DebugLn(' CompleteEventAssignment: Initializing CodeCompletion...');
{$ENDIF}
// initialize class for code completion
CodeCompleteClassNode:=AClassNode;
CodeCompleteSrcChgCache:=SourceChangeCache;
// insert new published method to class
Beauty:=SourceChangeCache.BeautifyCodeOptions;
MethodAttr:=[phpWithStart, phpWithoutClassKeyword, phpWithVarModifiers,
phpWithParameterNames,phpWithDefaultValues,phpWithResultType];
MethodDefinition:=TrimCodeSpace(ProcContext.Tool.ExtractProcHead(
ProcContext.Node,
MethodAttr+[phpWithoutClassName,phpWithoutName]));
MethodDefinition:=Beauty.AddClassAndNameToProc(MethodDefinition, '', AnEventName);
{$IFDEF CTDEBUG}
DebugLn(' CompleteEventAssignment: Add Method To Class...');
{$ENDIF}
if not ProcExistsInCodeCompleteClass(CleanMethodDefinition) then begin
// insert method definition into class
if Interactive then
begin
if not ShowCodeCreationDlg(Beauty.BeautifyProc(MethodDefinition, 0, False), True, CCOptions) then
Exit;
end else
CCOptions.ClassSection := Beauty.MethodDefaultSection;
AddClassInsertion(CleanMethodDefinition, MethodDefinition,
AnEventName, InsertClassSectionToNewProcClassPart[CCOptions.ClassSection]);
end;
MethodDefinition:=Beauty.AddClassAndNameToProc(MethodDefinition,
ExtractClassName(AClassNode,false,true), AnEventName);
if not InsertAllNewClassParts then
RaiseException(20170421201451,ctsErrorDuringInsertingNewClassParts);
// insert all missing proc bodies
if not CreateMissingClassProcBodies(false) then
RaiseException(20170421201453,ctsErrorDuringCreationOfNewProcBodies);
Result := True;
end;
procedure TCodeCompletionCodeTool.AddProcedureCompatibleToProcType(
const NewProcName: string; ProcContext: TFindContext; out
MethodDefinition: string; out MethodAttr: TProcHeadAttributes;
SourceChangeCache: TSourceChangeCache; CursorNode: TCodeTreeNode);
var
StartNode: TCodeTreeNode;
Node: TCodeTreeNode;
InFrontOfNode: TCodeTreeNode;
Indent: Integer;
InsertPos: Integer;
NewProc: String;
Beauty: TBeautifyCodeOptions;
begin
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// find a nice insert position in front of methods and CursorNode
StartNode:=FindImplementationNode;
if (StartNode=nil) and (Tree.Root.Desc<>ctnUnit) then
StartNode:=Tree.Root;
InFrontOfNode:=nil;
if StartNode<>nil then begin
Node:=StartNode.FirstChild;
while Node<>nil do begin
if (CursorNode<>nil) and (Node.StartPos>CursorNode.StartPos) then break;
if Node.Desc<>ctnUsesSection then
InFrontOfNode:=Node;
if NodeIsMethodBody(Node)
or (Node.Desc in [ctnBeginBlock,ctnAsmBlock]) then
break;
Node:=Node.NextBrother;
end;
end;
if InFrontOfNode<>nil then begin
// insert in front
Indent:=Beauty.GetLineIndent(Src,InFrontOfNode.StartPos);
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InFrontOfNode.StartPos);
end else begin
Node:=FindMainUsesNode(false);
if Node<>nil then begin
// insert behind uses section
Indent:=Beauty.GetLineIndent(Src,Node.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(Node.EndPos);
end else begin
// insert at start
if StartNode=nil then begin
// unit without implementation
RaiseException(20170421201459,'need implementation section to insert new procedure');
end;
Node:=StartNode.Next;
if Node<>nil then begin
// insert in front of second node
InsertPos:=Node.StartPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end else if StartNode.Desc=ctnImplementation then begin
// empty implementation => insert at start
Indent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
InsertPos:=StartNode.StartPos+length('implementation');
end else begin
// empty program
RaiseException(20170421201504,'no insert place found for the new procedure');
end;
end;
end;
// extract method param list, result type and modifiers
MethodAttr:=[phpWithStart, phpWithoutClassKeyword, phpWithVarModifiers,
phpWithParameterNames,phpWithDefaultValues,phpWithResultType,
phpWithCallingSpecs];
MethodDefinition:=TrimCodeSpace(
ProcContext.Tool.ExtractProcHead(ProcContext.Node,
MethodAttr+[phpWithoutClassName,phpWithoutName]));
if MethodDefinition='' then
RaiseException(20170422200434,'unknown proctype '+ProcContext.Node.DescAsString);
MethodDefinition:=Beauty.AddClassAndNameToProc(MethodDefinition, '', NewProcName);
debugln(['TCodeCompletionCodeTool.AddProcedureCompatibleToProcType MethodDefinition="',MethodDefinition,'"']);
// create code and insert
NewProc:=Beauty.BeautifyProc(MethodDefinition,Indent,true);
debugln(['TCodeCompletionCodeTool.AddProcedureCompatibleToProcType NewProc="',NewProc,'"']);
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,NewProc)
then
RaiseException(20170421201508,'unable to insert code at '+CleanPosToStr(InsertPos,true));
end;
procedure TCodeCompletionCodeTool.AddNeededUnitsToMainUsesSectionForRange(
StartPos, EndPos: integer; CompletionTool: TCodeCompletionCodeTool);
var
Params: TFindDeclarationParams;
OldCursor: TAtomPosition;
ContextNode: TCodeTreeNode;
NewUnitName: String;
begin
Params:=nil;
ContextNode:=nil;
try
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
if (CurPos.StartPos>EndPos) or (CurPos.Flag=cafNone) then exit;
if AtomIsIdentifier then begin
//DebugLn(['AddNeededUnitsForRange ',GetAtom]);
// save cursor
OldCursor:=CurPos;
// search identifier
if ContextNode=nil then
ContextNode:=FindDeepestNodeAtPos(CurPos.StartPos,true);
if Params=nil then
Params:=TFindDeclarationParams.Create(Self, ContextNode);
ContextNode := ContextNode.GetNodeOfType(ctnProcedureType);
Params.ContextNode:=ContextNode;
Params.SetIdentifier(Self,@Src[CurPos.StartPos],@CheckSrcIdentifier);
Params.Flags:=fdfDefaultForExpressions+[fdfExceptionOnPredefinedIdent];
try
//DebugLn(['TCodeCompletionCodeTool.AddNeededUnitsToMainUsesSectionForRange Identifier=',GetAtom]);
FindIdentifierInContext(Params);
// identifier found
NewUnitName:=Params.NewCodeTool.GetSourceName(false);
//DebugLn(['TCodeCompletionCodeTool.AddNeededUnitsToMainUsesSectionForRange NewUnitName=',NewUnitName]);
if NewUnitName<>'' then
CompletionTool.AddNeededUnitToMainUsesSection(PChar(NewUnitName));
except
on E: ECodeToolError do;
end;
// restore cursor
MoveCursorToAtomPos(OldCursor);
end;
until false;
finally
Params.Free;
end;
end;
procedure TCodeCompletionCodeTool.CalcMemSize(Stats: TCTMemStats);
begin
inherited CalcMemSize(Stats);
Stats.Add('TCodeCompletionCodeTool',
MemSizeString(FSetPropertyVariablename)
+PtrUInt(SizeOf(FSetPropertyVariableIsPrefix))
+PtrUInt(SizeOf(FSetPropertyVariableUseConst))
+MemSizeString(FJumpToProcHead.Name)
+MemSizeString(FJumpToProcHead.ResultType)
+PtrUInt(SizeOf(FJumpToProcHead.Group))
+length(NewClassSectionIndent)*SizeOf(integer)
+length(NewClassSectionInsertPos)*SizeOf(integer)
+MemSizeString(fFullTopLvlName));
if fNewMainUsesSectionUnits<>nil then
Stats.Add('TCodeCompletionCodeTool.fNewMainUsesSectionUnits',
SizeOf(TAVLTreeNode)*fNewMainUsesSectionUnits.Count);
end;
function TCodeCompletionCodeTool.CompleteClass(AClassNode: TCodeTreeNode;
CleanCursorPos, OldTopLine: integer; CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine, BlockTopLine,
BlockBottomLine: integer): boolean;
var
SectionNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
Result:=true;
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode In-a-class ',NodeDescriptionAsString(AClassNode.Desc));
{$ENDIF}
// cursor is in class/object definition
if (AClassNode.SubDesc and ctnsForwardDeclaration)>0 then exit;
CheckWholeUnitParsed(AClassNode,CursorNode);
// parse class and build CodeTreeNodes for all properties/methods
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode C ',dbgs(CleanCursorPos),', |',copy(Src,CleanCursorPos,8));
{$ENDIF}
CodeCompleteClassNode:=AClassNode;
try
// go through all properties and procs
// insert read + write prop specifiers
// demand Variables + Procs + Proc Bodies
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode Complete Properties ... ');
{$ENDIF}
if CodeCompleteClassNode.Desc in AllClassObjects then
SectionNode:=CodeCompleteClassNode.FirstChild
else
SectionNode:=CodeCompleteClassNode;
while SectionNode<>nil do begin
ANode:=SectionNode.FirstChild;
while ANode<>nil do begin
if ANode.Desc=ctnProperty then begin
// check if property is complete
if not CompleteProperty(ANode) then
RaiseException(20170421201511,ctsUnableToCompleteProperty);
end;
ANode:=ANode.NextBrother;
end;
if SectionNode=CodeCompleteClassNode then break;
SectionNode:=SectionNode.NextBrother;
end;
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode Apply ... ');
{$ENDIF}
// apply the changes and jump to first new proc body
Result:=ApplyChangesAndJumpToFirstNewProc(CleanCursorPos,OldTopLine,true,
NewPos,NewTopLine, BlockTopLine, BlockBottomLine);
finally
FreeClassInsertionList;
end;
end;
function TCodeCompletionCodeTool.CompleteForwardProcs(
CursorPos: TCodeXYPosition; ProcNode, CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine, BlockTopLine,
BlockBottomLine: integer; SourceChangeCache: TSourceChangeCache): boolean;
// add proc bodies for forward procs
// or update signatures
const
ProcAttrDefToBody = [phpWithStart,
phpWithVarModifiers,
phpWithParameterNames,phpWithResultType,phpWithCallingSpecs];
var
RevertableJump: boolean;
ProcDefNodes, ProcBodyNodes: TAVLTree;
StartProcNode: TCodeTreeNode;
CurProcNode: TCodeTreeNode;
EndProcNode: TCodeTreeNode;
ProcCode: String;
Indent: integer;
InsertPos: integer;
Beauty: TBeautifyCodeOptions;
NodeExt: TCodeTreeNodeExtension;
ProcsCopied: boolean;
StartNode: TCodeTreeNode;
OnlyNode: TCodeTreeNode;
begin
Result:=true;
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode in a forward procedure ... ');
{$ENDIF}
CheckWholeUnitParsed(CursorNode,ProcNode);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
ProcDefNodes:=nil;
ProcBodyNodes:=nil;
try
// gather all proc definitions
StartNode:=nil;
if (ProcNode.Parent.Desc=ctnImplementation) then begin
StartNode:=FindInterfaceNode;
if StartNode<>nil then
StartNode:=StartNode.FirstChild;
end;
if StartNode=nil then
StartNode:=FindFirstNodeOnSameLvl(ProcNode);
//debugln(['TCodeCompletionCodeTool.CompleteForwardProcs StartNode=',StartNode.DescAsString,' at ',CleanPosToStr(StartNode.StartPos),'=',ExtractProcName(StartNode,[])]);
ProcDefNodes:=GatherProcNodes(StartNode,
[phpInUpperCase,phpIgnoreProcsWithBody,phpIgnoreMethods],'');
// gather all proc bodies
ProcBodyNodes:=GatherProcNodes(FindNextNodeOnSameLvl(ProcNode),
[phpInUpperCase,phpIgnoreForwards,phpIgnoreMethods],'');
//debugln(['TCodeCompletionCodeTool.CompleteForwardProcs Defs=',ProcDefNodes.Count,' Bodies=',ProcBodyNodes.Count]);
// create mapping from proc defs to proc bodies
GuessProcDefBodyMapping(ProcDefNodes,ProcBodyNodes,true,false);
ProcCode:=ExtractProcHead(ProcNode,[phpInUpperCase]);
NodeExt:=FindNodeExtInTree(ProcDefNodes,ProcCode);
if (NodeExt<>nil) and (NodeExt.Data<>nil) then begin
// proc has already a body => update signatures
//debugln(['TCodeCompletionCodeTool.CompleteForwardProcs proc body already exists, updating signatures ...']);
if Beauty.UpdateMultiProcSignatures then
OnlyNode:=nil
else
OnlyNode:=ProcNode;
if not UpdateProcBodySignatures(ProcDefNodes,ProcBodyNodes,
ProcAttrDefToBody,ProcsCopied,OnlyNode) then exit;
if not SourceChangeCache.Apply then
RaiseException(20170421201515,'CompleteForwardProcs: unable to apply changes');
exit;
end;
// find first forward proc without body
StartProcNode:=ProcNode;
CurProcNode:=StartProcNode;
repeat
ProcCode:=ExtractProcHead(CurProcNode,[phpInUpperCase]);
if (FindNodeExtInTree(ProcBodyNodes,ProcCode)<>nil)
or (ProcNodeHasSpecifier(CurProcNode,psEXTERNAL)) then begin
// node is already completed
if CurProcNode=ProcNode then begin
// cursor node is already completed -> stop completion
exit;
end;
break;
end;
StartProcNode:=CurProcNode;
CurProcNode:=CurProcNode.PriorBrother;
until (CurProcNode=nil) or (CurProcNode.Desc<>ctnProcedure)
or ((CurProcNode.SubDesc and ctnsForwardDeclaration)=0);
// find last forward proc without body
EndProcNode:=ProcNode;
CurProcNode:=EndProcNode.NextBrother;
while (CurProcNode<>nil) and (CurProcNode.Desc=ctnProcedure)
and ((CurProcNode.SubDesc and ctnsForwardDeclaration)>0) do begin
ProcCode:=ExtractProcHead(CurProcNode,[phpInUpperCase]);
if (FindNodeExtInTree(ProcBodyNodes,ProcCode)<>nil)
or (ProcNodeHasSpecifier(CurProcNode,psEXTERNAL)) then begin
// node is already completed
break;
end;
EndProcNode:=CurProcNode;
CurProcNode:=CurProcNode.NextBrother;
end;
// find a nice insert position
FindInsertPositionForForwardProc(SourceChangeCache,StartProcNode,
Indent,InsertPos);
// build nice procs
CurProcNode:=StartProcNode;
repeat
ProcCode:=ExtractProcHead(CurProcNode,[phpWithStart,
phpWithoutClassKeyword,
phpWithVarModifiers,phpWithParameterNames,phpWithResultType,
phpWithCallingSpecs,phpWithAssembler,phpDoNotAddSemicolon]);
if ProcCode='' then
RaiseException(20170421201518,'CompleteForwardProcs: unable to parse forward proc node');
if ProcCode[length(ProcCode)]<>';' then begin
// add missing semicolon
ProcCode:=ProcCode+';';
UndoReadNextAtom;
if not SourceChangeCache.Replace(gtNone,gtNone,
CurPos.EndPos,CurPos.EndPos,';') then
RaiseException(20170421201522,'CompleteForwardProcs: unable to insert semicolon');
end;
ProcCode:=Beauty.BeautifyProc(ProcCode,Indent,true);
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
InsertPos,InsertPos,ProcCode) then
RaiseException(20170421201525,'CompleteForwardProcs: unable to insert new proc body');
// next
if CurProcNode=EndProcNode then break;
CurProcNode:=FindNextNodeOnSameLvl(CurProcNode);
until false;
if not SourceChangeCache.Apply then
RaiseException(20170421201528,'CompleteForwardProcs: unable to apply changes');
// reparse code and find jump point into new proc
Result:=FindJumpPoint(CursorPos,NewPos,NewTopLine,BlockTopLine, BlockBottomLine, RevertableJump);
finally
DisposeAVLTree(ProcDefNodes);
DisposeAVLTree(ProcBodyNodes);
end;
end;
function TCodeCompletionCodeTool.CompleteVariableAssignment(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode; var NewPos: TCodeXYPosition;
var NewTopLine: integer; SourceChangeCache: TSourceChangeCache; Interactive: Boolean
): boolean;
var
VarNameAtom, AssignmentOperator, TermAtom: TAtomPosition;
NewType: string;
Params: TFindDeclarationParams;
ExprType: TExpressionType;
MissingUnit, NewName: String;
ResExprContext, OrigExprContext: TFindContext;
ProcNode, ClassNode: TCodeTreeNode;
CCOptions: TCodeCreationDlgResult;
begin
Result:=false;
{$IFDEF VerboseCompleteLocalVarAssign}
DebugLn(' CompleteLocalVariableAssignment: A');
{$ENDIF}
if not ((CursorNode.Desc=ctnBeginBlock)
or CursorNode.HasParentOfType(ctnBeginBlock)) then exit;
if CursorNode.Desc=ctnBeginBlock then
BuildSubTreeForBeginBlock(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
{$IFDEF VerboseCompleteLocalVarAssign}
DebugLn(' CompleteLocalVariableAssignment: B CheckLocalVarAssignmentSyntax ...');
{$ENDIF}
// check assignment syntax
if not CheckLocalVarAssignmentSyntax(CleanCursorPos,
VarNameAtom,AssignmentOperator,TermAtom)
then begin
{$IFDEF VerboseCompleteLocalVarAssign}
debugln(['TCodeCompletionCodeTool.CompleteLocalVariableAssignment CheckLocalVarAssignmentSyntax=false']);
{$ENDIF}
exit;
end;
{$IFDEF VerboseCompleteLocalVarAssign}
debugln(['TCodeCompletionCodeTool.CompleteLocalVariableAssignment VarNameAtom=',dbgstr(Src,VarNameAtom.StartPos,VarNameAtom.EndPos-VarNameAtom.StartPos),' AssignmentOperator=',dbgstr(Src,AssignmentOperator.StartPos,AssignmentOperator.EndPos-AssignmentOperator.StartPos),' TermAtom=',dbgstr(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos)]);
{$ENDIF}
// search variable
ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
{$IFDEF VerboseCompleteLocalVarAssign}
DebugLn(' CompleteLocalVariableAssignment: check if variable is already defined ...');
{$ENDIF}
// check if identifier exists
Result:=IdentifierIsDefined(VarNameAtom,CursorNode,Params);
//debugln(['TCodeCompletionCodeTool.CompleteLocalVariableAssignment Identifier=',dbgstr(Src,VarNameAtom.StartPos,VarNameAtom.EndPos-VarNameAtom.StartPos),' exists=',Result]);
if Result then begin
MoveCursorToCleanPos(VarNameAtom.StartPos);
ReadNextAtom;
RaiseExceptionFmt(20170421201531,ctsIdentifierAlreadyDefined,[GetAtom]);
end;
{$IFDEF VerboseCompleteLocalVarAssign}
DebugLn(' CompleteLocalVariableAssignment: Find type of term ...',
' Term="',copy(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos),'"');
{$ENDIF}
// find type of term
Params.ContextNode:=CursorNode;
if Beautifier.OverrideStringTypesWithFirstParamType then
Params.Flags:=Params.Flags+[fdfOverrideStringTypesWithFirstParamType];
NewType:=FindTermTypeAsString(TermAtom,Params,ExprType);
if NewType='' then
RaiseException(20170421201534,'CompleteLocalVariableAssignment Internal error: NewType=""');
// check if there is another NewType in context of CursorNode
if (ExprType.Desc = xtContext) and (ExprType.Context.Tool <> nil) then
begin
Params.SetIdentifier(Self, PChar(NewType), nil);
Params.ContextNode := CursorNode;
Params.Flags := [fdfSearchInAncestors..fdfIgnoreCurContextNode,fdfTypeType,fdfSearchInHelpers];
if FindIdentifierInContext(Params) then
begin
ResExprContext:=Params.NewCodeTool.FindBaseTypeOfNode(
Params,Params.NewNode);
OrigExprContext:=ExprType.Context.Tool.FindBaseTypeOfNode(
Params,ExprType.Context.Node);
if (ResExprContext.Tool <> OrigExprContext.Tool) then // the "source" types are different -> add unit to the type
NewType := ExprType.Context.Tool.ExtractSourceName + '.' + NewType
else
begin // the "source" types are the same -> set ExprType to found Params.New* so that unit adding is avoided (with MissingUnit)
ExprType.Context.Tool:=Params.NewCodeTool;
ExprType.Context.Node:=Params.NewNode;
end;
end;
end;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
MissingUnit:='';
if (ExprType.Desc=xtContext)
and (ExprType.Context.Tool<>nil) then
MissingUnit:=GetUnitNameForUsesSection(ExprType.Context.Tool);
NewName := GetAtom(VarNameAtom);
FindProcAndClassNode(CursorNode, ProcNode, ClassNode);
if Interactive and (ClassNode<>nil) then
begin
Result:=True;
if not ShowCodeCreationDlg(NewName+': '+NewType+';', False, CCOptions) then
Exit;
end else
CCOptions.Location := cclLocal;
if CCOptions.Location=cclLocal then
Result:=AddLocalVariable(CleanCursorPos,OldTopLine,NewName,
NewType,MissingUnit,NewPos,NewTopLine,SourceChangeCache)
else
begin
// initialize class for code completion
CodeCompleteClassNode:=ClassNode;
CodeCompleteSrcChgCache:=SourceChangeCache;
AddClassInsertion(UpperCase(NewName)+';', NewName+':'+NewType+';',
NewName, InsertClassSectionToNewVarClassPart[CCOptions.ClassSection]);
if not InsertAllNewClassParts then
RaiseException(20170421201536,ctsErrorDuringInsertingNewClassParts);
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201538,ctsUnableToApplyChanges);
end;
end;
function TCodeCompletionCodeTool.CompleteEventAssignment(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode; out
IsEventAssignment: boolean; var NewPos: TCodeXYPosition;
var NewTopLine: integer; SourceChangeCache: TSourceChangeCache;
Interactive: Boolean): boolean;
{ examples:
Button1.OnClick:=|
OnClick:=@AnEve|nt
with Button1 do OnMouseDown:=@|
If OnClick is a method then it will be completed to
Button1.OnClick:=@Button1Click;
and a 'procedure Button1Click(Sender: TObject);' with a method body will
be added to the published section of the class of the Begin..End Block.
}
function CheckEventAssignmentSyntax(out PropVarAtom: TAtomPosition;
out AssignmentOperator, AddrOperatorPos: integer;
out UserEventAtom: TAtomPosition;
out SemicolonPos: integer): boolean;
begin
Result:=false;
// check if in begin..end block
if not ((CursorNode.Desc=ctnBeginBlock)
or CursorNode.HasParentOfType(ctnBeginBlock)) then exit;
// read event name (optional)
while (CleanCursorPos<SrcLen)
and (Src[CleanCursorPos] in [':','=',' ',#9]) do
inc(CleanCursorPos);
GetIdentStartEndAtPosition(Src,CleanCursorPos,
UserEventAtom.StartPos,UserEventAtom.EndPos);
MoveCursorToAtomPos(UserEventAtom);
if AtomIsKeyWord then exit;
ReadPriorAtom;
// check @ operator (optional)
if AtomIsChar('@') then begin
AddrOperatorPos:=CurPos.StartPos;
ReadPriorAtom;
end else
AddrOperatorPos:=-1;
// check assignment operator :=
if not AtomIs(':=') then exit;
ReadPriorAtom;
AssignmentOperator:=CurPos.EndPos;
// check event name
if not AtomIsIdentifier then exit;
PropVarAtom:=CurPos;
// check for semicolon at end of statement
MoveCursorToCleanPos(UserEventAtom.EndPos);
ReadNextAtom;
if CurPos.Flag = cafRoundBracketOpen then
if Scanner.CompilerMode <> cmDELPHI then
Exit // indeed it is assignment to function, e.g. x:=sin(y);
else begin
ReadNextAtom;
if CurPos.Flag <> cafRoundBracketClose then
Exit; // in Delhi mode empty brackets are allowed after method: OnClick:=FormCreate();
ReadNextAtom;
end;
if AtomIsChar(';') then
SemicolonPos:=CurPos.StartPos
else
SemicolonPos:=-1;
{$IFDEF CTDEBUG}
DebugLn(' CheckEventAssignmentSyntax: "',copy(Src,PropertyAtom.StartPos,
UserEventAtom.EndPos-PropertyAtom.StartPos),'"');
{$ENDIF}
Result:=true;
end;
function FindEventTypeAtCursor(PropVarAtom: TAtomPosition;
out PropVarContext, ProcContext: TFindContext;
Params: TFindDeclarationParams): boolean;
begin
Result:=false;
// find declaration of property identifier
Params.ContextNode:=CursorNode;
MoveCursorToCleanPos(PropVarAtom.StartPos);
Params.SetIdentifier(Self,@Src[CurPos.StartPos],nil);
fFullTopLvlName:='';
Params.OnTopLvlIdentifierFound:=@OnTopLvlIdentifierFound;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFindVariable];
if (not FindDeclarationOfIdentAtParam(Params)) then begin
{$IFDEF CTDEBUG}
DebugLn('FindEventTypeAtCursor identifier "',GetIdentifier(@Src[CurPos.StartPos]),'" not found');
{$ENDIF}
exit;
end;
if not (Params.NewNode.Desc in [ctnProperty,ctnVarDefinition]) then begin
{$IFDEF CTDEBUG}
DebugLn('FindEventTypeAtCursor not a property/variable');
{$ENDIF}
exit;
end;
PropVarContext:=CreateFindContext(Params);
// identifier is property
// -> check type of property
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers];
ProcContext:=PropVarContext.Tool.FindBaseTypeOfNode(
Params,PropVarContext.Node);
if (ProcContext.Node=nil)
or not (ProcContext.Node.Desc in AllProcTypes)
then begin
{$IFDEF CTDEBUG}
DebugLn('FindEventTypeAtCursor not a procedure type');
{$ENDIF}
exit;
end;
// identifier is property/var of type proc => this is an event
Result:=true;
end;
function CreateEventFullName(AClassNode: TCodeTreeNode; UserEventAtom,
PropVarAtom: TAtomPosition): string;
var PropVarName, AClassName: string;
l: integer;
begin
if UserEventAtom.StartPos=UserEventAtom.EndPos then begin
Result:=fFullTopLvlName;
l:=PropVarAtom.EndPos-PropVarAtom.StartPos;
PropVarName:=copy(Src,PropVarAtom.StartPos,l);
if SysUtils.CompareText(PropVarName,RightStr(Result,l))<>0 then
Result:=Result+PropVarName;
if SysUtils.CompareText(PropVarName,Result)=0 then begin
// this is an event of the class (not event of published objects)
// -> add form name
MoveCursorToNodeStart(AClassNode.Parent);
ReadNextAtom;
AClassName:=GetAtom;
if (length(AClassName)>1) and (AClassName[1] in ['t','T']) then
System.Delete(AClassName,1,1);
Result:=AClassName+Result;
end;
// convert OnClick to Click
if (UpperCaseStr(LeftStr(PropVarName,2))='ON')
and (SysUtils.CompareText(RightStr(Result,l),PropVarName)=0)
then
Result:=LeftStr(Result,length(Result)-l)+RightStr(Result,l-2);
end else begin
Result:=copy(Src,UserEventAtom.StartPos,
UserEventAtom.EndPos-UserEventAtom.StartPos);
end;
{$IFDEF CTDEBUG}
DebugLn('CreateEventFullName "',Result,'"');
{$ENDIF}
end;
function CompleteAssignment(const AnEventName: string;
AssignmentOperator, AddrOperatorPos, SemicolonPos: integer;
UserEventAtom: TAtomPosition): boolean;
var RValue: string;
StartInsertPos, EndInsertPos: integer;
begin
{$IFDEF CTDEBUG}
DebugLn(' CompleteEventAssignment: Changing right side of assignment...');
{$ENDIF}
// add new event name as right value of assignment
// add address operator @ if needed or user provided it himself
RValue:=AnEventName+';';
if (AddrOperatorPos>0)
or ((Scanner.PascalCompiler=pcFPC) and (Scanner.CompilerMode<>cmDelphi))
then
RValue:='@'+RValue;
RValue:=':='+RValue;
RValue:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(RValue,0);
StartInsertPos:=AssignmentOperator;
EndInsertPos:=SemicolonPos+1;
if EndInsertPos<1 then
EndInsertPos:=UserEventAtom.EndPos;
if EndInsertPos<1 then
EndInsertPos:=AddrOperatorPos;
if EndInsertPos<1 then
EndInsertPos:=AssignmentOperator+2;
Result:=SourceChangeCache.Replace(gtNone,gtNewLine,
StartInsertPos,EndInsertPos,RValue);
end;
procedure AddProcedure(Identifier: string;
TypeTool: TFindDeclarationTool; TypeNode: TCodeTreeNode);
var
ProcContext: TFindContext;
AMethodDefinition: string;
AMethodAttr: TProcHeadAttributes;
begin
// create new method
ProcContext:=CreateFindContext(TypeTool,TypeNode);
AddProcedureCompatibleToProcType(Identifier,
ProcContext,AMethodDefinition,AMethodAttr,SourceChangeCache,
CursorNode);
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201540,ctsUnableToApplyChanges);
{$IFDEF CTDEBUG}
DebugLn(' CompleteEventAssignment.AddProcedure: jumping to new method body...');
{$ENDIF}
// jump to new method body
if not JumpToMethod(AMethodDefinition,AMethodAttr,NewPos,NewTopLine)
then
RaiseException(20170421201543,'CompleteEventAssignment.AddProcedure JumpToMethod failed');
end;
// function CompleteEventAssignment: boolean
var
UserEventAtom, PropVarAtom: TAtomPosition;
AssignmentOperator, AddrOperatorPos, SemicolonPos: integer;
Params: TFindDeclarationParams;
PropertyContext, ProcContext: TFindContext;
FullEventName, AMethodDefinition: string;
AMethodAttr: TProcHeadAttributes;
ProcNode, AClassNode: TCodeTreeNode;
Identifier: String;
begin
IsEventAssignment:=false;
Result:=false;
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: CheckEventAssignmentSyntax...');
{$ENDIF}
// check assigment syntax
if not CheckEventAssignmentSyntax(PropVarAtom, AssignmentOperator,
AddrOperatorPos, UserEventAtom, SemicolonPos)
then
exit;
IsEventAssignment:=true;
if OldTopLine=0 then ;
ProcNode:=nil;
AClassNode:=nil;
CheckWholeUnitParsed(CursorNode,ProcNode);
if CursorNode.Desc=ctnBeginBlock then
BuildSubTreeForBeginBlock(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: check if a method and find class...');
{$ENDIF}
FindProcAndClassNode(CursorNode,ProcNode,AClassNode);
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: FindEventTypeAtCursor...');
{$ENDIF}
// check if identifier is event property and build
Result:=FindEventTypeAtCursor(PropVarAtom,PropertyContext,ProcContext,
Params);
if not Result then exit;
if ((AClassNode<>nil) and (ProcContext.Node.Desc=ctnReferenceTo))
or ProcContext.Tool.ProcNodeHasOfObject(ProcContext.Node) then begin
if AClassNode<>nil then begin
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: CreateEventFullName... UserEventAtom.StartPos=',dbgs(UserEventAtom.StartPos));
{$ENDIF}
// create a nice event name
FullEventName:=CreateEventFullName(AClassNode,UserEventAtom,PropVarAtom);
if FullEventName='' then exit;
// add published method and method body and right side of assignment
if not AddMethodCompatibleToProcType(AClassNode,FullEventName,ProcContext,
AMethodDefinition,AMethodAttr,SourceChangeCache,Interactive)
then
Exit;
if not CompleteAssignment(FullEventName,AssignmentOperator,
AddrOperatorPos,SemicolonPos,UserEventAtom)
then
RaiseException(20170421201546,'CompleteEventAssignment CompleteAssignment failed');
end else if ProcContext.Tool.ProcNodeHasOfObject(ProcContext.Node) then begin
{$IFDEF VerboseCompleteEventAssign}
debugln([' CompleteEventAssignment: proc is "of object"']);
{$ENDIF}
MoveCursorToCleanPos(PropVarAtom.StartPos);
RaiseException(20170421201550,'Complete event failed: procedure of object needs a class');
end;
end else begin
// create procedure (not method)
{$IFDEF VerboseCompleteEventAssign}
debugln([' CompleteEventAssignment: create a proc name']);
{$ENDIF}
// get name
Identifier:='';
if (UserEventAtom.StartPos>1) and (UserEventAtom.StartPos<=SrcLen) then
Identifier:=GetIdentifier(@Src[UserEventAtom.StartPos]);
if Identifier='' then
Identifier:=GetIdentifier(@Src[PropVarAtom.StartPos]);
if Identifier='' then begin
MoveCursorToCleanPos(PropVarAtom.StartPos);
RaiseException(20170421201553,'Complete event failed: need a name');
end;
// create proc
{$IFDEF VerboseCompleteEventAssign}
debugln([' CompleteEventAssignment: create a proc name']);
{$ENDIF}
AddProcedureCompatibleToProcType(Identifier,
ProcContext,AMethodDefinition,AMethodAttr,SourceChangeCache,
CursorNode);
end;
finally
Params.Free;
end;
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: Applying changes...');
{$ENDIF}
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201555,ctsUnableToApplyChanges);
{$IFDEF VerboseCompleteEventAssign}
DebugLn(' CompleteEventAssignment: jumping to new method body...');
{$ENDIF}
// jump to new method body
if not JumpToMethod(AMethodDefinition,AMethodAttr,NewPos,NewTopLine)
then
RaiseException(20170421201558,'CompleteEventAssignment Internal Error 2');
Result:=true;
end;
function TCodeCompletionCodeTool.CompleteVariableForIn(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode; var NewPos: TCodeXYPosition;
var NewTopLine: integer; SourceChangeCache: TSourceChangeCache; Interactive: Boolean
): boolean;
var
VarNameAtom: TAtomPosition;
TermAtom: TAtomPosition;
Params: TFindDeclarationParams;
NewType: String;
ExprType: TExpressionType;
MissingUnit: String;
begin
Result:=false;
{$IFDEF CTDEBUG}
DebugLn(' CompleteLocalVariableForIn: A');
{$ENDIF}
if not ((CursorNode.Desc=ctnBeginBlock)
or CursorNode.HasParentOfType(ctnBeginBlock)) then exit;
if CursorNode.Desc=ctnBeginBlock then
BuildSubTreeForBeginBlock(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
{$IFDEF CTDEBUG}
DebugLn(' CompleteLocalVariableForIn: B CheckLocalVarForInSyntax ...');
{$ENDIF}
// check assignment syntax
if not CheckLocalVarForInSyntax(CleanCursorPos,
VarNameAtom,TermAtom)
then
exit;
DebugLn(['TCodeCompletionCodeTool.CompleteLocalVariableForIn Var=',GetAtom(VarNameAtom),' Term=',GetAtom(TermAtom)]);
// search variable
ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
{$IFDEF CTDEBUG}
DebugLn(' CompleteLocalVariableForIn: check if variable is already defined ...');
{$ENDIF}
// check if identifier exists
Result:=IdentifierIsDefined(VarNameAtom,CursorNode,Params);
if Result then begin
MoveCursorToCleanPos(VarNameAtom.StartPos);
ReadNextAtom;
RaiseExceptionFmt(20170421201601,ctsIdentifierAlreadyDefined,[GetAtom]);
end;
{$IFDEF CTDEBUG}
DebugLn(' CompleteLocalVariableForIn: Find type of term ...',
' Term="',copy(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos),'"');
{$ENDIF}
// find type of term
NewType:=FindForInTypeAsString(TermAtom,CursorNode,Params,ExprType);
if NewType='' then
RaiseException(20170421201604,'CompleteLocalVariableForIn Internal error: NewType=""');
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
MissingUnit:='';
if (ExprType.Desc=xtContext)
and (ExprType.Context.Tool<>nil) then
MissingUnit:=GetUnitNameForUsesSection(ExprType.Context.Tool);
Result:=AddLocalVariable(CleanCursorPos,OldTopLine,GetAtom(VarNameAtom),
NewType,MissingUnit,NewPos,NewTopLine,SourceChangeCache);
end;
function TCodeCompletionCodeTool.CompleteIdentifierByParameter(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode; var NewPos: TCodeXYPosition;
var NewTopLine: integer; SourceChangeCache: TSourceChangeCache; Interactive: Boolean
): boolean;
procedure AddMethod(Identifier: string;
TypeTool: TFindDeclarationTool; TypeNode: TCodeTreeNode);
var
AMethodAttr: TProcHeadAttributes;
AMethodDefinition: string;
ProcContext: TFindContext;
AClassNode: TCodeTreeNode;
begin
// parameter needs a method => search class of method
AClassNode:=FindClassOrInterfaceNode(CursorNode,true);
if (AClassNode=nil) then
RaiseException(20170421201607,'parameter needs a method');
ProcContext:=CreateFindContext(TypeTool,TypeNode);
// create new method
if not AddMethodCompatibleToProcType(AClassNode,Identifier,
ProcContext,AMethodDefinition,AMethodAttr,SourceChangeCache,Interactive)
then
Exit;
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201609,ctsUnableToApplyChanges);
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter.AddMethod: jumping to new method body...');
{$ENDIF}
// jump to new method body
if not JumpToMethod(AMethodDefinition,AMethodAttr,NewPos,NewTopLine)
then
RaiseException(20170421201612,'CompleteIdentifierByParameter.AddMethod JumpToMethod failed');
end;
procedure AddProcedure(Identifier: string;
TypeTool: TFindDeclarationTool; TypeNode: TCodeTreeNode);
var
ProcContext: TFindContext;
AMethodDefinition: string;
AMethodAttr: TProcHeadAttributes;
begin
// create new method
ProcContext:=CreateFindContext(TypeTool,TypeNode);
AddProcedureCompatibleToProcType(Identifier,
ProcContext,AMethodDefinition,AMethodAttr,SourceChangeCache,
CursorNode);
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201614,ctsUnableToApplyChanges);
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter.AddProcedure: jumping to new method body...');
{$ENDIF}
// jump to new method body
if not JumpToMethod(AMethodDefinition,AMethodAttr,NewPos,NewTopLine)
then
RaiseException(20170421201617,'CompleteIdentifierByParameter.AddProcedure JumpToMethod failed');
end;
var
VarNameRange, ProcNameAtom: TAtomPosition;
ParameterIndex: integer;
Params: TFindDeclarationParams;
ParameterNode: TCodeTreeNode;
TypeNode: TCodeTreeNode;
NewType: String;
IgnorePos: TCodePosition;
MissingUnitName: String;
ProcStartPos: LongInt;
ExprType: TExpressionType;
Context: TFindContext;
HasAtOperator: Boolean;
TypeTool: TFindDeclarationTool;
AliasType: TFindContext;
Identifier: String;
begin
Result:=false;
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter: A');
{$ENDIF}
if not ((CursorNode.Desc=ctnBeginBlock)
or CursorNode.HasParentOfType(ctnBeginBlock)) then exit;
if CursorNode.Desc=ctnBeginBlock then
BuildSubTreeForBeginBlock(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter: B check if it is a parameter ...');
{$ENDIF}
// check parameter syntax
if not CheckParameterSyntax(CursorNode.StartPos,CleanCursorPos,
VarNameRange,ProcNameAtom,ParameterIndex)
then
exit;
HasAtOperator:=false;
if (VarNameRange.StartPos<=SrcLen)
and (Src[VarNameRange.StartPos]='@') then begin
HasAtOperator:=true;
MoveCursorToCleanPos(VarNameRange.StartPos+1);
ReadNextAtom;
VarNameRange.StartPos:=CurPos.StartPos;
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter HasAtOperator ',GetAtom(VarNameRange)]);
end;
Identifier:=ExtractCode(VarNameRange.StartPos,VarNameRange.EndPos,[]);
if not IsValidIdent(Identifier) then exit;
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter VarNameAtom=',GetAtom(VarNameAtom),' ProcNameAtom=',GetAtom(ProcNameAtom),' ParameterIndex=',dbgs(ParameterIndex));
{$ENDIF}
// search variable
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter: check if variable is already defined ...');
{$ENDIF}
// check if identifier exists
Result:=IdentifierIsDefined(VarNameRange,CursorNode,Params);
if Result then begin
MoveCursorToCleanPos(VarNameRange.StartPos);
ReadNextAtom;
RaiseExceptionFmt(20170421201619,ctsIdentifierAlreadyDefined,[GetAtom]);
end;
{$IFDEF CTDEBUG}
DebugLn(' CompleteIdentifierByParameter: Find declaration of parameter list ... procname="',GetAtom(ProcNameAtom),'"');
{$ENDIF}
Context:=CreateFindContext(Self,CursorNode);
ProcStartPos:=FindStartOfTerm(ProcNameAtom.EndPos,false);
if ProcStartPos<ProcNameAtom.StartPos then begin
// for example: Canvas.Line
// find class
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter Call="',ExtractCode(ProcStartPos,ProcNameAtom.EndPos,[]),'"']);
{$ENDIF}
Params.ContextNode:=Context.Node;
Params.Flags:=fdfDefaultForExpressions+[fdfFunctionResult,fdfFindChildren];
ExprType:=FindExpressionResultType(Params,ProcStartPos,ProcNameAtom.StartPos);
if not(ExprType.Desc in xtAllIdentTypes) then begin
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter Call="',ExtractCode(ProcStartPos,ProcNameAtom.StartPos,[]),'" gives ',ExprTypeToString(ExprType)]);
exit;
end;
Context:=ExprType.Context;
if Assigned(Context.Tool) and Assigned(Context.Node) then
begin
// resolve point '.'
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter base class: ',FindContextToString(Context)]);
Params.Clear;
Params.Flags:=fdfDefaultForExpressions;
Context:=Context.Tool.FindBaseTypeOfNode(Params,Context.Node);
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter search proc in sub context: ',FindContextToString(Context)]);
{$ENDIF}
end;
end;
if Assigned(Context.Tool) and Assigned(Context.Node) then
begin
// find declaration of parameter list
// ToDo: search in all overloads for the best fit
Params.ContextNode:=Context.Node;
Params.SetIdentifier(Self,@Src[ProcNameAtom.StartPos],nil);
Params.Flags:=fdfDefaultForExpressions+[fdfFindVariable];
if Context.Node=CursorNode then
Params.Flags:=Params.Flags+[fdfSearchInParentNodes,fdfIgnoreCurContextNode]
else
Params.Flags:=Params.Flags-[fdfSearchInParentNodes,fdfIgnoreCurContextNode];
CleanPosToCodePos(VarNameRange.StartPos,IgnorePos);
IgnoreErrorAfter:=IgnorePos;
try
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter searching ',GetIdentifier(Params.Identifier),' [',dbgs(Params.Flags),'] in ',FindContextToString(Context)]);
{$ENDIF}
if not Context.Tool.FindIdentifierInContext(Params) then exit;
finally
ClearIgnoreErrorAfter;
end;
end else
if (ExprType.Desc in xtAllTypeHelperTypes) then
begin
Params.ContextNode:=CursorNode;
Params.SetIdentifier(Self,@Src[ProcNameAtom.StartPos],nil);
Params.Flags:=fdfDefaultForExpressions+[fdfFindVariable]+
[fdfSearchInParentNodes,fdfIgnoreCurContextNode];
FindIdentifierInBasicTypeHelpers(ExprType.Desc, Params);
end;
NewType:='';
MissingUnitName:='';
if Params.NewNode=nil then exit;
//DebugLn('TCodeCompletionCodeTool.CompleteLocalVariableAsParameter Proc/PropNode=',Params.NewNode.DescAsString,' ',copy(Params.NewCodeTool.Src,Params.NewNode.StartPos,50));
if Params.NewNode.Desc=ctnVarDefinition then
begin
try
ExprType:=Params.NewCodeTool.ConvertNodeToExpressionType(Params.NewNode,Params);
if (ExprType.Desc=xtContext) and (ExprType.Context.Node<>nil) then begin
Params.NewCodeTool:=ExprType.Context.Tool;
Params.NewNode:=ExprType.Context.Node;
end;
except
end;
end;
ParameterNode:=Params.NewCodeTool.FindNthParameterNode(Params.NewNode,
ParameterIndex);
if (ParameterNode=nil)
and (Params.NewNode.Desc in [ctnProperty,ctnProcedure]) then begin
DebugLn([' CompleteIdentifierByParameter Procedure has less than ',ParameterIndex+1,' parameters']);
exit;
end;
if ParameterNode=nil then exit;
//DebugLn('TCodeCompletionCodeTool.CompleteIdentifierByParameter ParameterNode=',ParameterNode.DescAsString,' ',copy(Params.NewCodeTool.Src,ParameterNode.StartPos,50));
TypeTool:=Params.NewCodeTool;
TypeNode:=FindTypeNodeOfDefinition(ParameterNode);
if TypeNode=nil then begin
DebugLn(' CompleteIdentifierByParameter Parameter has no type');
exit;
end;
// default: copy the type
NewType:=TypeTool.ExtractCode(TypeNode.StartPos,TypeNode.EndPos,[]);
// search type
Params.Clear;
Params.ContextNode:=TypeNode;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving];
AliasType:=CleanFindContext;
ExprType:=TypeTool.FindExpressionResultType(Params,
TypeNode.StartPos,TypeNode.EndPos,@AliasType);
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter parameter type: AliasType=',FindContextToString(AliasType)]);
TypeTool:=ExprType.Context.Tool;
TypeNode:=ExprType.Context.Node;
if HasAtOperator
or ((Scanner.CompilerMode=cmDelphi) and (ExprType.Desc=xtContext) // procedures in delphi mode without @
and (TypeNode<>nil) and (TypeNode.Desc in AllProcTypes)) then
begin
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter HasAtOperator ExprType=',ExprTypeToString(ExprType)]);
NewType:='';
if (ExprType.Desc<>xtContext)
or (TypeNode=nil) then begin
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter parameter has @ operator, but this is not implemented for ',ExprTypeToString(ExprType)]);
exit;
end;
if (TypeNode.Desc=ctnPointerType) then begin
// for example PMapID = ^...
if (TypeNode.FirstChild<>nil)
and (TypeNode.FirstChild.Desc=ctnIdentifier) then begin
// for example PMapID = ^TMapID
NewType:=TypeTool.ExtractCode(TypeNode.FirstChild.StartPos,
TypeNode.FirstChild.EndPos,[]);
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter pointer to ',NewType]);
Params.Clear;
Params.ContextNode:=TypeNode;
Params.Flags:=fdfDefaultForExpressions;
AliasType:=CleanFindContext;
ExprType:=TypeTool.FindExpressionResultType(Params,
TypeNode.FirstChild.StartPos,TypeNode.FirstChild.EndPos,
@AliasType);
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter parameter is pointer to type: AliasType=',FindContextToString(AliasType)]);
end;
end else if TypeNode.Desc in AllProcTypes then begin
// for example TNotifyEvent = procedure(...
if TypeTool.ProcNodeHasOfObject(TypeNode) then begin
AddMethod(Identifier,TypeTool,TypeNode);
end else begin
// parameter needs a procedure
AddProcedure(Identifier,TypeTool,TypeNode);
end;
exit(true);
end;
if NewType='' then begin
debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter parameter has @ operator, but this is not implemented for ',ExprTypeToString(ExprType)]);
exit;
end;
end;
if AliasType.Node<>nil then begin
// an identifier
MissingUnitName:=GetUnitNameForUsesSection(AliasType.Tool);
//debugln(['TCodeCompletionCodeTool.CompleteIdentifierByParameter MissingUnitName=',MissingUnitName]);
end;
//DebugLn('TCodeCompletionCodeTool.CompleteIdentifierByParameter NewType=',NewType);
if NewType='' then
RaiseException(20170421201622,'CompleteIdentifierByParameter Internal error: NewType=""');
//DebugLn(' CompleteIdentifierByParameter Dont know: ',Params.NewNode.DescAsString);
finally
Params.Free;
end;
Result:=AddLocalVariable(CleanCursorPos,OldTopLine,GetAtom(VarNameRange),
NewType,MissingUnitName,NewPos,NewTopLine,SourceChangeCache);
end;
function TCodeCompletionCodeTool.CompleteMethodByBody(
CleanCursorPos, OldTopLine: integer;
CursorNode: TCodeTreeNode;
var NewPos: TCodeXYPosition; var NewTopLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
const
ProcAttrCopyBodyToDef = [phpWithStart,phpWithoutClassName,phpWithVarModifiers,
phpWithParameterNames,phpWithDefaultValues,phpWithResultType];
procedure MergeProcModifiers(DefProcNode, BodyProcNode: TCodeTreeNode;
var ProcCode: String);
var
FirstBodyModAtom: TAtomPosition;
BodyHeadEnd: Integer;
DefHeadEnd: Integer;
Modifier: shortstring;
OldCursor: TAtomPosition;
AddModifier: boolean;
begin
MoveCursorToFirstProcSpecifier(DefProcNode);
if DefProcNode.FirstChild<>nil then
DefHeadEnd:=DefProcNode.FirstChild.EndPos
else
DefHeadEnd:=DefProcNode.EndPos;
FirstBodyModAtom:=CleanAtomPosition;
BodyHeadEnd:=0;
while CurPos.EndPos<DefHeadEnd do begin
if CurPos.Flag<>cafSemicolon then begin
// a modifier of the definition
Modifier:=copy(GetAtom,1,255);
//debugln(['MergeProcModifiers body modifier: ',Modifier]);
if not IsKeyWordCallingConvention.DoItCaseInsensitive(Modifier) then
begin
// test if body already has this modifier
OldCursor:=CurPos;
if BodyHeadEnd=0 then begin
MoveCursorToFirstProcSpecifier(BodyProcNode);
FirstBodyModAtom:=CurPos;
if BodyProcNode.FirstChild<>nil then
BodyHeadEnd:=BodyProcNode.FirstChild.EndPos
else
BodyHeadEnd:=BodyProcNode.EndPos;
end else
MoveCursorToAtomPos(FirstBodyModAtom);
while CurPos.EndPos<BodyHeadEnd do begin
if CurPos.Flag<>cafSemicolon then begin
if AtomIs(Modifier) then break;
// skip to next modifier of body
repeat
ReadNextAtom;
until (CurPos.Flag=cafSemicolon) or (CurPos.EndPos>=BodyHeadEnd);
end else
ReadNextAtom;
end;
AddModifier:=CurPos.EndPos>=BodyHeadEnd;
MoveCursorToAtomPos(OldCursor);
end else
AddModifier:=false;
// skip to next modifier of definition
repeat
if AddModifier then begin
if (IsIdentStartChar[Src[CurPos.StartPos]]
and IsIdentChar[ProcCode[length(ProcCode)]]) // space needed between words
or IsSpaceChar[Src[CurPos.StartPos-1]] // copy space from body
then
ProcCode:=ProcCode+' ';
ProcCode:=ProcCode+GetAtom;
end;
ReadNextAtom;
until (CurPos.Flag=cafSemicolon) or (CurPos.EndPos>=DefHeadEnd);
if AddModifier then
ProcCode:=ProcCode+';';
end else
ReadNextAtom;
end;
end;
var
CurClassName: String;
BodyProcNode: TCodeTreeNode;
CleanProcCode: String;
ProcName: String;
OldCodePos: TCodePosition;
ClassProcs: TAVLTree;
ProcBodyNodes: TAVLTree;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
DefProcNode: TCodeTreeNode;
NewProcCode: String;
OldProcCode: String;
FromPos: Integer;
EndPos: Integer;
Indent: Integer;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
// check if cursor in a method
if CursorNode.Desc=ctnProcedure then
BodyProcNode:=CursorNode
else
BodyProcNode:=CursorNode.GetNodeOfType(ctnProcedure);
if (BodyProcNode=nil) or (BodyProcNode.Desc<>ctnProcedure)
or (not NodeIsMethodBody(BodyProcNode)) then begin
{$IFDEF VerboseCompleteMethod}
DebugLn(['TCodeCompletionCodeTool.CompleteMethodByBody node is not a method body ',BodyProcNode<>nil]);
{$ENDIF}
exit;
end;
CheckWholeUnitParsed(CursorNode,BodyProcNode);
// find corresponding class declaration
CurClassName:=ExtractClassNameOfProcNode(BodyProcNode);
if CurClassName='' then begin
DebugLn(['CompleteMethodByBody ExtractClassNameOfProcNode failed']);
exit;
end;
//DebugLn(['CompleteMethod CurClassName=',CurClassName]);
CodeCompleteClassNode:=FindClassNodeInUnit(CurClassName,true,false,false,true);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
ClassProcs:=nil;
ProcBodyNodes:=nil;
try
// find the corresponding node in the class
DefProcNode:=nil;
// gather existing proc definitions in the class
ClassProcs:=GatherClassProcDefinitions(CodeCompleteClassNode,true);
CleanProcCode:=ExtractProcHead(BodyProcNode,[phpInUpperCase]);
NodeExt:=FindCodeTreeNodeExt(ClassProcs,CleanProcCode);
if NodeExt<>nil then begin
DefProcNode:=TCodeTreeNodeExtension(NodeExt).Node;
end else begin
// the proc was not found by name+params
// => guess
ProcBodyNodes:=GatherClassProcBodies(CodeCompleteClassNode);
GuessProcDefBodyMapping(ClassProcs,ProcBodyNodes,true,true);
AVLNode:=ProcBodyNodes.FindLowest;
NodeExt:=nil;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node=BodyProcNode then begin
if NodeExt.Data<>nil then
DefProcNode:=TCodeTreeNodeExtension(NodeExt.Data).Node;
break;
end;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
end;
end;
if DefProcNode<>nil then begin
// update existing definition
{$IFDEF VerboseCompleteMethod}
DebugLn(['TCodeCompletionCodeTool.CompleteMethodByBody corresponding definition exists for "',CleanProcCode,'"']);
{$ENDIF}
OldProcCode:=ExtractProcHead(DefProcNode,ProcAttrCopyBodyToDef+[phpWithProcModifiers]);
NewProcCode:=ExtractProcHead(BodyProcNode,ProcAttrCopyBodyToDef+[phpWithCallingSpecs]);
// some modifiers are only allowed in the definition
// => keep the old definition modifiers
MergeProcModifiers(DefProcNode,BodyProcNode,NewProcCode);
if CompareTextIgnoringSpace(NewProcCode,OldProcCode,false)=0 then
exit(true); // already matching
// ToDo: definition needs update
{$IFDEF VerboseCompleteMethod}
debugln(['TCodeCompletionCodeTool.CompleteMethodByBody OldProcCode="',OldProcCode,'"']);
debugln(['TCodeCompletionCodeTool.CompleteMethodByBody NewProcCode="',NewProcCode,'"']);
{$ENDIF}
// store old cursor position
if not CleanPosToCodePos(CleanCursorPos,OldCodePos) then begin
RaiseException(20170421201627,'TCodeCompletionCodeTool.CompleteMethodByBody Internal Error: '
+'CleanPosToCodePos');
end;
Indent:=Beauty.GetLineIndent(Src,DefProcNode.StartPos);
FromPos:=DefProcNode.StartPos;
EndPos:=DefProcNode.EndPos;
SourceChangeCache.MainScanner:=Scanner;
NewProcCode:=Beauty.BeautifyStatement(
NewProcCode,Indent,[bcfDoNotIndentFirstLine]);
{$IFDEF VerboseCompleteMethod}
debugln('TCodeCompletionCodeTool.CompleteMethodByBody final NewProcCode:');
debugln(NewProcCode);
{$ENDIF}
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,EndPos,NewProcCode)
then
exit;
Result:=SourceChangeCache.Apply;
end else begin
// insert new definition
ProcName:=ExtractProcName(BodyProcNode,[phpWithoutClassName]);
{$IFDEF VerboseCompleteMethod}
DebugLn(['TCodeCompletionCodeTool.CompleteMethodByBody Adding body to definition "',CleanProcCode,'"']);
{$ENDIF}
// store old cursor position
if not CleanPosToCodePos(CleanCursorPos,OldCodePos) then begin
RaiseException(20170421201630,'TCodeCompletionCodeTool.CompleteMethodByBody Internal Error: '
+'CleanPosToCodePos');
end;
CodeCompleteSrcChgCache:=SourceChangeCache;
// add method declaration
NewProcCode:=ExtractProcHead(BodyProcNode,ProcAttrCopyBodyToDef+[phpWithCallingSpecs]);
CleanProcCode:=ExtractProcHead(BodyProcNode,
[phpWithoutClassKeyword,phpWithoutClassName,phpInUpperCase]);
AddClassInsertion(CleanProcCode,NewProcCode,ProcName,ncpPrivateProcs);
// apply changes
Result:=ApplyClassCompletion(false);
end;
// adjust cursor position
AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine);
finally
DisposeAVLTree(ClassProcs);
DisposeAVLTree(ProcBodyNodes);
end;
{$IFDEF VerboseCompleteMethod}
DebugLn(['TCodeCompletionCodeTool.CompleteMethodByBody END OldCodePos.P=',OldCodePos.P,' OldTopLine=',OldTopLine,' NewPos=',Dbgs(NewPos),' NewTopLine=',NewTopLine]);
{$ENDIF}
end;
function TCodeCompletionCodeTool.CreateParamListFromStatement(
CursorNode: TCodeTreeNode; BracketOpenPos: integer; out CleanList: string
): string;
var
ParamNames: TStringToStringTree;
function CreateParamName(ExprStartPos, ExprEndPos: integer;
const ParamType: string): string;
var
i: Integer;
begin
Result:='';
// use the last identifier of expression as name
MoveCursorToCleanPos(ExprStartPos);
repeat
ReadNextAtom;
if AtomIsIdentifier then
Result:=GetAtom
else
Result:='';
until CurPos.EndPos>=ExprEndPos;
// otherwise use ParamType
if Result='' then
Result:=ParamType;
// otherwise use 'Param'
if not IsValidIdent(Result) then
Result:='Param';
// prepend an 'a'
if Result[1]<>'a' then
Result:='a'+Result;
// make unique
if ParamNames=nil then
ParamNames:=TStringToStringTree.Create(false);
if ParamNames.Contains(Result) then begin
i:=1;
while ParamNames.Contains(Result+IntToStr(i)) do inc(i);
Result:=Result+IntToStr(i);
end;
ParamNames[Result]:='used';
end;
var
i: Integer;
ExprList: TExprTypeList;
ParamExprType: TExpressionType;
ParamType: String;
ExprStartPos: LongInt;
ExprEndPos: LongInt;
Params: TFindDeclarationParams;
ParamName: String;
// create param list without brackets
{$IFDEF EnableCodeCompleteTemplates}
Colon : String;
{$ENDIF}
begin
Result:='';
CleanList:='';
ExprList:=nil;
ParamNames:=nil;
ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
// check parameter list
ExprList:=CreateParamExprListFromStatement(BracketOpenPos,Params);
// create parameter list
MoveCursorToCleanPos(BracketOpenPos);
ReadNextAtom;
//DebugLn(['TCodeCompletionCodeTool.CreateParamListFromStatement BracketClose=',BracketClose]);
for i:=0 to ExprList.Count-1 do begin
ReadNextAtom;
ExprStartPos:=CurPos.StartPos;
// read til comma or bracket close
repeat
//DebugLn(['TCodeCompletionCodeTool.CreateParamListFromStatement loop ',GetAtom]);
if (CurPos.StartPos>SrcLen)
or (CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose,cafComma])
then
break;
if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
ReadTilBracketClose(true);
end;
ReadNextAtom;
until false;
ExprEndPos:=CurPos.StartPos;
//DebugLn(['TCodeCompletionCodeTool.CreateParamListFromStatement Param=',copy(Src,ExprStartPos,ExprEndPos-ExprStartPos)]);
// get type
ParamExprType:=ExprList.Items[i];
ParamType:=FindExprTypeAsString(ParamExprType,ExprStartPos);
// create a nice parameter name
ParamName:=CreateParamName(ExprStartPos,ExprEndPos,ParamType);
//DebugLn(['TCodeCompletionCodeTool.CreateParamListFromStatement ',i,' ',ParamName,':',ParamType]);
if Result<>'' then begin
Result:=Result+';';
CleanList:=CleanList+';';
end;
{$IFDEF EnableCodeCompleteTemplates}
if assigned(CTTemplateExpander)
and CTTemplateExpander.TemplateExists('PrettyColon') then
begin
Colon := CTTemplateExpander.Expand('PrettyColon', '','', // Doesn't use linebreak or indentation
[], [] );
Result:=Result+ParamName+Colon+ParamType;
CleanList:=CleanList+Colon+ParamType;
end
else
{$ENDIF EnableCodeCompleteTemplates}
begin
Result:=Result+ParamName+':'+ParamType;
CleanList:=CleanList+':'+ParamType;
end;
// next
MoveCursorToCleanPos(ExprEndPos);
ReadNextAtom;
end;
finally
ExprList.Free;
Params.Free;
ParamNames.Free;
DeactivateGlobalWriteLock;
end;
end;
function TCodeCompletionCodeTool.CompleteProcByCall(CleanCursorPos,
OldTopLine: integer; CursorNode: TCodeTreeNode; var NewPos: TCodeXYPosition;
var NewTopLine, BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
// check if 'procname(expr list);'
const
ShortProcFormat = [phpWithoutClassKeyword];
function CheckProcSyntax(out BeginNode: TCodeTreeNode;
out ProcNameAtom: TAtomPosition;
out BracketOpenPos, BracketClosePos: LongInt): boolean;
begin
Result:=false;
// check if in a begin..end block
if CursorNode=nil then exit;
BeginNode:=CursorNode.GetNodeOfType(ctnBeginBlock);
if BeginNode=nil then exit;
// check if CleanCursorPos is valid
if (CleanCursorPos>SrcLen) then CleanCursorPos:=SrcLen;
if (CleanCursorPos<1) then exit;
// skip bracket
if (Src[CleanCursorPos]='(') then dec(CleanCursorPos);
// go to start of identifier
while (CleanCursorPos>1) and (IsIdentChar[Src[CleanCursorPos-1]]) do
dec(CleanCursorPos);
// read procname
MoveCursorToCleanPos(CleanCursorPos);
ReadNextAtom;
if not AtomIsIdentifier then exit;
ProcNameAtom:=CurPos;
// read bracket
ReadNextAtom;
if CurPos.Flag<>cafRoundBracketOpen then exit;
BracketOpenPos:=CurPos.StartPos;
// read bracket close
if not ReadTilBracketClose(false) then exit;
BracketClosePos:=CurPos.StartPos;
Result:=true;
end;
function CheckFunctionType(const ProcNameAtom: TAtomPosition;
out IsFunction: Boolean;
out FuncType: String;
out ProcExprStartPos: integer): boolean;
begin
Result:=false;
// find start of proc expression (e.g. Button1.Constrains.DoSomething)
IsFunction:=false;
FuncType:='';
ProcExprStartPos:=FindStartOfTerm(ProcNameAtom.EndPos,false);
if ProcExprStartPos<0 then exit;
MoveCursorToCleanPos(ProcExprStartPos);
ReadPriorAtom;
if (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen])
or (UpAtomIs(':=')) then begin
FuncType:='integer';
IsFunction:=true;
end;
Result:=true;
end;
function CheckProcDoesNotExist(Params: TFindDeclarationParams;
const ProcNameAtom: TAtomPosition): boolean;
begin
Result:=false;
// check if proc already exists
Params.ContextNode:=CursorNode;
Params.SetIdentifier(Self,@Src[ProcNameAtom.StartPos],@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,
fdfTopLvlResolving,fdfSearchInAncestors,fdfSearchInHelpers,
fdfIgnoreCurContextNode];
if FindIdentifierInContext(Params) then begin
// proc already exists
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall proc already exists']);
MoveCursorToCleanPos(ProcNameAtom.StartPos);
ReadNextAtom;
RaiseExceptionFmt(20170421201633,ctsIdentifierAlreadyDefined,[GetAtom]);
end;
Result:=true;
end;
function CreateProcCode(CursorNode: TCodeTreeNode;
const ProcNameAtom: TAtomPosition;
IsFunction: boolean; const FuncType: string;
BracketOpenPos, Indent: integer;
out CleanProcHead, ProcCode: string): boolean;
var
le: String;
ProcName: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// create param list
ProcCode:=CreateParamListFromStatement(CursorNode,BracketOpenPos,CleanProcHead);
if ProcCode<>'' then begin
ProcCode:='('+ProcCode+')';
CleanProcHead:='('+CleanProcHead+')';
end;
// prepend proc name
ProcName:=GetAtom(ProcNameAtom);
ProcCode:=ProcName+ProcCode;
CleanProcHead:=ProcName+CleanProcHead;
// prepend 'procedure' keyword
if IsFunction then
begin
{$IFDEF EnableCodeCompleteTemplates}
if (CTTemplateExpander<>nil)
and CTTemplateExpander.TemplateExists('PrettyColon') then
begin
ProcCode:= 'function '+ProcCode+
CTTemplateExpander.Expand('PrettyColon','','',[],[])
+FuncType+';';
end
else
{$ENDIF}
begin
ProcCode:='function '+ProcCode+':'+FuncType+';';
end;
end
else
ProcCode:='procedure '+ProcCode+';';
CleanProcHead:=CleanProcHead+';';
// append begin..end
le:=Beauty.LineEnd;
ProcCode:=ProcCode+le
+'begin'+le
+le
+'end;';
ProcCode:=Beauty.BeautifyStatement(ProcCode,Indent);
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall ',ProcCode]);
Result:=true;
end;
function CreatePathForNewProc(InsertPos: integer;
const CleanProcHead: string;
out NewProcPath: TStrings): boolean;
var
ContextNode: TCodeTreeNode;
begin
Result:=false;
// find context at insert position
ContextNode:=FindDeepestNodeAtPos(InsertPos,true);
if (ContextNode.Desc=ctnProcedure) and (ContextNode.StartPos=InsertPos)
or ((ContextNode.LastChild<>nil) and (ContextNode.LastChild.StartPos<InsertPos))
then
// ContextNode is a procedure below or above the insert position
// => after the insert the new proc will not be a child
// -> it will become a child of its parent
ContextNode:=ContextNode.Parent;
NewProcPath:=CreateSubProcPath(ContextNode,ShortProcFormat);
// add new proc
NewProcPath.Add(CleanProcHead);
DebugLn(['CreatePathForNewProc NewProcPath=',NewProcPath.Text]);
Result:=true;
end;
function FindJumpPointToNewProc(SubProcPath: TStrings): boolean;
var
NewProcNode: TCodeTreeNode;
begin
Result:=false;
// reparse code and find jump point into new proc
BuildTree(lsrInitializationStart);
NewProcNode:=FindSubProcPath(SubProcPath,ShortProcFormat,true);
if NewProcNode=nil then begin
debugln(['FindJumpPointToNewProc FindSubProcPath failed, SubProcPath="',SubProcPath.Text,'"']);
exit;
end;
Result:=FindJumpPointInProcNode(NewProcNode,NewPos,NewTopLine,BlockTopLine,BlockBottomLine);
{ $IFDEF CTDebug}
if Result then
DebugLn('TCodeCompletionCodeTool.CompleteProcByCall END ',NewProcNode.DescAsString,' ',dbgs(Result),' ',dbgs(NewPos.X),',',dbgs(NewPos.Y),' ',dbgs(NewTopLine));
{ $ENDIF}
end;
var
BeginNode: TCodeTreeNode;
ProcNameAtom: TAtomPosition;
BracketOpenPos, BracketClosePos: integer;
ExprType: TExpressionType;
Params: TFindDeclarationParams;
InsertPos: LongInt;
Indent: LongInt;
ExprList: TExprTypeList;
ProcNode: TCodeTreeNode;
ProcCode: String;
ProcExprStartPos: LongInt;
IsFunction: Boolean;
FuncType: String;
CleanProcHead: string;
NewProcPath: TStrings;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if not CheckProcSyntax(BeginNode,ProcNameAtom,BracketOpenPos,BracketClosePos)
then exit;
if OldTopLine=0 then ;
CheckWholeUnitParsed(CursorNode,BeginNode);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
Params:=TFindDeclarationParams.Create(Self, CursorNode);
ExprList:=nil;
ActivateGlobalWriteLock;
try
if not CheckFunctionType(ProcNameAtom,IsFunction,FuncType,ProcExprStartPos)
then exit;
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall Call="',copy(Src,ProcNameAtom.StartPos,BracketClosePos+1-ProcNameAtom.StartPos),'"']);
if not CheckProcDoesNotExist(Params,ProcNameAtom) then exit;
// find context (e.g. Button1.|)
Params.Clear;
Params.ContextNode:=CursorNode;
ExprType:=FindExpressionTypeOfTerm(-1,ProcNameAtom.StartPos,Params,false);
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall Context: ',ExprTypeToString(ExprType)]);
if ExprType.Desc=xtNone then begin
// default context
if NodeIsInAMethod(CursorNode) then begin
// eventually: create a new method
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall ToDo: create a new method']);
exit;
end else begin
ProcNode:=CursorNode.GetNodeOfType(ctnProcedure);
if ProcNode<>nil then begin
// this is a normal proc or nested proc
// insert new proc in front
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ProcNode.StartPos);
Indent:=Beauty.GetLineIndent(Src,ProcNode.StartPos);
debugln(['TCodeCompletionCodeTool.CompleteProcByCall insert as new proc in front of proc']);
end else begin
// this is a begin..end without proc (e.g. program or unit code)
// insert new proc in front
InsertPos:=FindLineEndOrCodeInFrontOfPosition(BeginNode.StartPos);
Indent:=Beauty.GetLineIndent(Src,BeginNode.StartPos);
debugln(['TCodeCompletionCodeTool.CompleteProcByCall insert as new proc in front of begin']);
end;
end;
end else begin
// eventually: create a new method in another class
DebugLn(['TCodeCompletionCodeTool.CompleteProcByCall ToDo: create a new method in another class']);
exit;
end;
if not CreateProcCode(CursorNode,ProcNameAtom,
IsFunction,FuncType,BracketOpenPos,Indent,
CleanProcHead,ProcCode)
then begin
debugln(['TCodeCompletionCodeTool.CompleteProcByCall CreateProcCode failed']);
exit;
end;
finally
DeactivateGlobalWriteLock;
Params.Free;
ExprList.Free;
end;
// insert proc body
//debugln(['TCodeCompletionCodeTool.CompleteProcByCall InsertPos=',CleanPosToStr(InsertPos),' ProcCode="',ProcCode,'"']);
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
InsertPos,InsertPos,ProcCode)
then
exit;
// remember old path
NewProcPath:=nil;
try
if not CreatePathForNewProc(InsertPos,CleanProcHead,NewProcPath) then begin
debugln(['TCodeCompletionCodeTool.CompleteProcByCall CreatePathForNewProc failed']);
exit;
end;
if not SourceChangeCache.Apply then begin
debugln(['TCodeCompletionCodeTool.CompleteProcByCall SourceChangeCache.Apply failed']);
exit;
end;
//debugln(['TCodeCompletionCodeTool.CompleteProcByCall ',TCodeBuffer(Scanner.MainCode).Source]);
if not FindJumpPointToNewProc(NewProcPath) then begin
debugln(['TCodeCompletionCodeTool.CompleteProcByCall FindJumpPointToNewProc(',NewProcPath.Text,') failed']);
exit;
end;
Result:=true;
finally
NewProcPath.Free;
end;
end;
procedure TCodeCompletionCodeTool.DoDeleteNodes(StartNode: TCodeTreeNode);
begin
inherited DoDeleteNodes(StartNode);
FCompletingCursorNode:=nil;
FreeClassInsertionList;
end;
function TCodeCompletionCodeTool.AddPublishedVariable(const UpperClassName,
VarName, VarType: string; SourceChangeCache: TSourceChangeCache): boolean;
begin
Result:=false;
if (UpperClassName='') or (VarName='') or (VarType='')
or (SourceChangeCache=nil) or (Scanner=nil) then exit;
// find classnode
BuildTree(lsrImplementationStart);
// initialize class for code completion
CodeCompleteClassNode:=FindClassNodeInInterface(UpperClassName,true,false,true);
CodeCompleteSrcChgCache:=SourceChangeCache;
// check if variable already exists
if not VarExistsInCodeCompleteClass(UpperCaseStr(VarName)) then begin
{$IFDEF EnableCodeCompleteTemplates}
if (CTTemplateExpander<>nil)
and CTTemplateExpander.TemplateExists('PrettyColon') then
begin
AddClassInsertion(UpperCaseStr(VarName),
VarName+CTTemplateExpander.Expand('PrettyColon','','',[],[])
+VarType+';',VarName,ncpPublishedVars);
end
else
{$ENDIF}
AddClassInsertion(UpperCaseStr(VarName),
VarName+':'+VarType+';',VarName,ncpPublishedVars);
if not InsertAllNewClassParts then
RaiseException(20170421201635,ctsErrorDuringInsertingNewClassParts);
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201637,ctsUnableToApplyChanges);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.GetRedefinitionNodeText(Node: TCodeTreeNode
): string;
begin
case Node.Desc of
ctnProcedure:
Result:=ExtractProcHead(Node,[phpInUpperCase,phpWithoutSemicolon]);
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnEnumIdentifier,
ctnGenericType:
Result:=ExtractDefinitionName(Node);
else
Result:='';
end;
end;
function TCodeCompletionCodeTool.FindRedefinitions(
out TreeOfCodeTreeNodeExt: TAVLTree; WithEnums: boolean): boolean;
var
AllNodes: TAVLTree;
procedure AddRedefinition(Redefinition, Definition: TCodeTreeNode;
const NodeText: string);
var
NodeExt: TCodeTreeNodeExtension;
begin
DebugLn(['AddRedefinition ',NodeText,' Redefined=',CleanPosToStr(Redefinition.StartPos),' Definition=',CleanPosToStr(Definition.StartPos)]);
//DebugLn(['AddRedefinition as source: Definition="',ExtractNode(Definition,[]),'" Redefinition="',ExtractNode(Redefinition,[]),'"']);
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=Redefinition;
NodeExt.Data:=Definition;
NodeExt.Txt:=NodeText;
if TreeOfCodeTreeNodeExt=nil then
TreeOfCodeTreeNodeExt:=TAVLTree.Create(@CompareCodeTreeNodeExt);
TreeOfCodeTreeNodeExt.Add(NodeExt);
end;
procedure AddDefinition(Node: TCodeTreeNode; const NodeText: string);
var
NodeExt: TCodeTreeNodeExtension;
begin
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=Node;
NodeExt.Txt:=NodeText;
AllNodes.Add(NodeExt);
end;
var
Node: TCodeTreeNode;
NodeText: String;
AVLNode: TAVLTreeNode;
begin
Result:=false;
TreeOfCodeTreeNodeExt:=nil;
BuildTree(lsrImplementationStart);
AllNodes:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnImplementation, ctnInitialization, ctnFinalization,
ctnBeginBlock, ctnAsmBlock:
// skip implementation
break;
ctnVarDefinition, ctnTypeDefinition, ctnConstDefinition, ctnProcedure,
ctnEnumIdentifier, ctnGenericType:
begin
NodeText:=GetRedefinitionNodeText(Node);
AVLNode:=FindCodeTreeNodeExtAVLNode(AllNodes,NodeText);
if AVLNode<>nil then begin
AddRedefinition(Node,TCodeTreeNodeExtension(AVLNode.Data).Node,NodeText);
Node:=Node.NextSkipChilds;
end else begin
AddDefinition(Node,NodeText);
if WithEnums
and (Node.FirstChild<>nil)
and (Node.FirstChild.Desc=ctnEnumerationType) then
Node:=Node.FirstChild
else
Node:=Node.NextSkipChilds;
end;
end;
else
Node:=Node.Next;
end;
end;
finally
DisposeAVLTree(AllNodes);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.RemoveRedefinitions(
TreeOfCodeTreeNodeExt: TAVLTree;
SourceChangeCache: TSourceChangeCache): boolean;
var
AVLNode: TAVLTreeNode;
NodesToDo: TAVLTree;// tree of TCodeTreeNode
Node: TCodeTreeNode;
StartNode: TCodeTreeNode;
EndNode: TCodeTreeNode;
IsListStart: Boolean;
IsListEnd: Boolean;
StartPos: LongInt;
EndPos: LongInt;
begin
Result:=false;
if SourceChangeCache=nil then exit;
if (TreeOfCodeTreeNodeExt=nil) or (TreeOfCodeTreeNodeExt.Count=0) then
exit(true);
SourceChangeCache.MainScanner:=Scanner;
NodesToDo:=TAVLTree.Create;
try
// put the nodes to remove into the NodesToDo
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
Node:=TCodeTreeNodeExtension(AVLNode.Data).Node;
//DebugLn(['TCodeCompletionCodeTool.RemoveRedefinitions add to NodesToDo ',GetRedefinitionNodeText(Node)]);
NodesToDo.Add(Node);
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
// delete all redefinitions
while NodesToDo.Count>0 do begin
// find a block of redefinitions
StartNode:=TCodeTreeNode(NodesToDo.Root.Data);
//DebugLn(['TCodeCompletionCodeTool.RemoveRedefinitions StartNode=',StartNode.StartPos,' ',GetRedefinitionNodeText(StartNode)]);
EndNode:=StartNode;
while (StartNode.PriorBrother<>nil)
and (NodesToDo.Find(StartNode.PriorBrother)<>nil) do
StartNode:=StartNode.PriorBrother;
while (EndNode.NextBrother<>nil)
and (NodesToDo.Find(EndNode.NextBrother)<>nil) do
EndNode:=EndNode.NextBrother;
//DebugLn(['TCodeCompletionCodeTool.RemoveRedefinitions Start=',StartNode.StartPos,' ',GetRedefinitionNodeText(StartNode),' End=',EndNode.StartPos,' ',GetRedefinitionNodeText(EndNode)]);
// check if a whole section is deleted
if (StartNode.PriorBrother=nil) and (EndNode.NextBrother=nil)
and (StartNode.Parent<>nil)
and (StartNode.Parent.Desc in AllDefinitionSections) then begin
StartNode:=StartNode.Parent;
EndNode:=StartNode;
end;
// compute nice code positions to delete
StartPos:=FindLineEndOrCodeInFrontOfPosition(StartNode.StartPos);
EndPos:=FindLineEndOrCodeAfterPosition(EndNode.EndPos);
// check list of definitions
if EndNode.Desc in AllIdentifierDefinitions then begin
// check list definition. For example:
// delete, delete: char; -> delete whole
// a,delete, delete: char; -> a: char;
// delete,delete,c: char; -> c: char;
// a,delete,delete,c: char; -> a,c:char;
IsListStart:=(StartNode.PriorBrother=nil)
or ((StartNode.PriorBrother<>nil)
and (StartNode.PriorBrother.FirstChild<>nil));
IsListEnd:=(EndNode.FirstChild<>nil);
if IsListStart and IsListEnd then begin
// case 1: delete, delete: char; -> delete whole
end else begin
// case 2-4: keep type
// get start position of first deleting identifier
StartPos:=StartNode.StartPos;
// get end position of last deleting identifier
EndPos:=EndNode.StartPos+GetIdentLen(@Src[EndNode.StartPos]);
if IsListEnd then begin
// case 2: a,delete, delete: char; -> a: char;
// delete comma in front of start too
MoveCursorToCleanPos(StartNode.PriorBrother.StartPos);
ReadNextAtom; // read identifier
ReadNextAtom; // read comma
StartPos:=CurPos.StartPos;
end else begin
// case 3,4
// delete comma behind end too
MoveCursorToCleanPos(EndNode.StartPos);
ReadNextAtom; // read identifier
ReadNextAtom; // read comma
EndPos:=CurPos.StartPos;
end;
end;
end;
// replace
DebugLn(['TCodeCompletionCodeTool.RemoveRedefinitions deleting:']);
debugln('"',copy(Src,StartPos,EndPos-StartPos),'"');
if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
exit;
// remove nodes from NodesToDo
Node:=StartNode;
repeat
NodesToDo.Remove(Node);
//DebugLn(['TCodeCompletionCodeTool.RemoveRedefinitions removed ',Node.StartPos,' ',GetRedefinitionNodeText(Node),' ',NodesToDo.Find(Node)<>nil]);
Node:=Node.Next;
until (Node=nil) or
((Node.StartPos>EndNode.StartPos) and (not Node.HasAsParent(EndNode)));
end;
finally
NodesToDo.Free;
end;
Result:=SourceChangeCache.Apply;
end;
function TCodeCompletionCodeTool.FindAliasDefinitions(out
TreeOfCodeTreeNodeExt: TAVLTree; OnlyWrongType: boolean): boolean;
// finds all public definitions of the form 'const A = B;'
var
AllNodes: TAVLTree;
procedure CheckAlias(Node: TCodeTreeNode);
var
ReferingNode: TCodeTreeNode;
ReferingNodeText: String;
ReferingPos: LongInt;
NodeExt: TCodeTreeNodeExtension;
BracketStartPos: LongInt;
NeededType: TCodeTreeNodeDesc;
procedure GetReferingNode;
begin
if ReferingNodeText<>'' then exit;
ReferingNodeText:=GetIdentifier(@Src[ReferingPos]);
NodeExt:=FindCodeTreeNodeExtWithIdentifier(AllNodes,PChar(ReferingNodeText));
if (NodeExt<>nil) then
ReferingNode:=NodeExt.Node;
end;
begin
// check if definition is an alias
// Example: const A = B; or const A = B();
if (Node.Parent=nil) then exit;
if not (Node.Parent.Desc in [ctnConstSection,ctnTypeSection]) then exit;
// this is a const or type
MoveCursorToNodeStart(Node);
// read A
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
// read =
ReadNextAtom;
if CurPos.Flag<>cafEqual then exit;
// read B
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
ReferingPos:=CurPos.StartPos;
ReadNextAtom;
if CurPos.Flag=cafRoundBracketOpen then begin
BracketStartPos:=CurPos.StartPos;
ReadTilBracketClose(true);
//BracketEndPos:=CurPos.StartPos;
ReadNextAtom;
end else
BracketStartPos:=0;
if CurPos.Flag<>cafSemicolon then exit;
ReferingNode:=nil;
NeededType:=ctnNone;
if BracketStartPos>0 then begin
if WordIsKeyWord.DoItCaseInsensitive(@Src[ReferingPos]) then
exit;
// this is a type cast
NeededType:=ctnConstDefinition;
//GetReferingNode;
if (ReferingNode<>nil) then begin
// ToDo: check if it is a typecast to a procedure type
// then the alias should be replaced with a procdure
//if (ReferingNode=ctnTypeDefinition)
end;
end else begin
// this is a const or type alias
//DebugLn(['TCodeCompletionCodeTool.FindAliasDefinitions Alias: ',Node.DescAsString,' ',ExtractNode(Node,[])]);
GetReferingNode;
if (ReferingNode<>nil) then begin
NeededType:=ReferingNode.Desc;
end;
end;
if NeededType=ctnNone then exit;
// add alias
if NeededType<>Node.Desc then begin
DebugLn(['TCodeCompletionCodeTool.FindAliasDefinitions Wrong: ',Node.DescAsString,' ',ExtractNode(Node,[]),' ',Node.DescAsString,'<>',NodeDescToStr(NeededType)]);
end;
if TreeOfCodeTreeNodeExt=nil then
TreeOfCodeTreeNodeExt:=TAVLTree.Create(@CompareCodeTreeNodeExt);
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=Node;
NodeExt.Txt:=GetRedefinitionNodeText(Node);
NodeExt.Data:=ReferingNode;
NodeExt.Flags:=NeededType;
TreeOfCodeTreeNodeExt.Add(NodeExt);
end;
procedure UpdateDefinition(const NodeText: string; Node: TCodeTreeNode);
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
AVLNode:=FindCodeTreeNodeExtAVLNode(AllNodes,NodeText);
if AVLNode=nil then begin
// add new node
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=Node;
NodeExt.Txt:=NodeText;
AllNodes.Add(NodeExt);
end else begin
// update node
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
NodeExt.Node:=Node;
end;
end;
procedure CollectAllDefinitions;
var
Node: TCodeTreeNode;
begin
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnImplementation, ctnInitialization, ctnFinalization,
ctnBeginBlock, ctnAsmBlock:
// skip implementation
break;
ctnTypeDefinition, ctnConstDefinition:
begin
// remember the definition
UpdateDefinition(GetRedefinitionNodeText(Node),Node);
Node:=Node.NextSkipChilds;
end;
ctnProcedure:
begin
UpdateDefinition(ExtractProcName(Node,[]),Node);
Node:=Node.NextSkipChilds;
end;
else
Node:=Node.Next;
end;
end;
end;
procedure CollectAllAliasDefinitions;
var
Node: TCodeTreeNode;
begin
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnImplementation, ctnInitialization, ctnFinalization,
ctnBeginBlock, ctnAsmBlock:
// skip implementation
break;
ctnTypeDefinition, ctnConstDefinition:
begin
CheckAlias(Node);
Node:=Node.NextSkipChilds;
end;
ctnProcedure:
Node:=Node.NextSkipChilds;
else
Node:=Node.Next;
end;
end;
end;
procedure ResolveAliases;
function FindAliasRoot(Node: TCodeTreeNode;
out NeededRootDesc: TCodeTreeNodeDesc): TCodeTreeNode;
var
AliasText: String;
AVLNode: TAVLTreeNode;
ReferingNode: TCodeTreeNode;
OldDesc: TCodeTreeNodeDesc;
NodeExt: TCodeTreeNodeExtension;
begin
Result:=Node;
NeededRootDesc:=Node.Desc;
if Node.Desc=ctnProcedure then
AliasText:=ExtractProcName(Node,[])
else
AliasText:=GetRedefinitionNodeText(Node);
if AliasText='' then exit;
AVLNode:=FindCodeTreeNodeExtAVLNode(TreeOfCodeTreeNodeExt,AliasText);
if AVLNode=nil then exit;
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
NeededRootDesc:=TCodeTreeNodeDesc(NodeExt.Flags);
ReferingNode:=TCodeTreeNode(NodeExt.Data);
if ReferingNode=nil then exit;
// this is an alias => search further
if ReferingNode.Desc=ctnNone then begin
// circle
exit;
end;
// mark node as visited
OldDesc:=Node.Desc;
Node.Desc:=ctnNone;
Result:=FindAliasRoot(ReferingNode,NeededRootDesc);
// unmark node as visited
Node.Desc:=OldDesc;
if NeededRootDesc=ctnNone then
NeededRootDesc:=Node.Desc;
end;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
ReferingNode: TCodeTreeNode;
NeededType: TCodeTreeNodeDesc;
begin
if TreeOfCodeTreeNodeExt=nil then exit;
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
ReferingNode:=TCodeTreeNode(NodeExt.Data);
if ReferingNode<>nil then begin
// this node is an alias.
// => find the root alias
ReferingNode:=FindAliasRoot(ReferingNode,NeededType);
NodeExt.Data:=ReferingNode;
NodeExt.Flags:=NeededType;
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
end;
procedure RemoveGoodAliases;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
NeededType: TCodeTreeNodeDesc;
NextAVLNode: TAVLTreeNode;
begin
if TreeOfCodeTreeNodeExt=nil then exit;
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NextAVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
NeededType:=TCodeTreeNodeDesc(NodeExt.Flags);
if NodeExt.Node.Desc=NeededType then begin
TreeOfCodeTreeNodeExt.FreeAndDelete(AVLNode);
end;
AVLNode:=NextAVLNode;
end;
end;
begin
Result:=false;
TreeOfCodeTreeNodeExt:=nil;
BuildTree(lsrImplementationStart);
AllNodes:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
if OnlyWrongType then
CollectAllDefinitions;
CollectAllAliasDefinitions;
if OnlyWrongType then begin
ResolveAliases;
RemoveGoodAliases;
end;
finally
DisposeAVLTree(AllNodes);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.FixAliasDefinitions(
TreeOfCodeTreeNodeExt: TAVLTree; SourceChangeCache: TSourceChangeCache
): boolean;
{ replaces public dummy functions with a constant.
The function body will be removed.
See the function FindAliasDefinitions.
}
function FindReferingNodeExt(DefNode: TCodeTreeNode): TCodeTreeNodeExtension;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node=DefNode then begin
Result:=NodeExt;
exit;
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=nil;
end;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
DefNode: TCodeTreeNode;
ReferingNode: TCodeTreeNode;
NextAVLNode: TAVLTreeNode;
ReferingNodeInFront: TCodeTreeNodeExtension;
ReferingNodeBehind: TCodeTreeNodeExtension;
NewSrc: String;
FromPos: LongInt;
ToPos: LongInt;
ReferingType: TCodeTreeNodeDesc;
NewSection: String;
ProcName: String;
OldProcName: String;
begin
Result:=false;
if SourceChangeCache=nil then exit;
if (TreeOfCodeTreeNodeExt=nil) or (TreeOfCodeTreeNodeExt.Count=0) then
exit(true);
SourceChangeCache.MainScanner:=Scanner;
// remove all nodes which can not be handled here
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NextAVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
DefNode:=NodeExt.Node;
ReferingType:=TCodeTreeNodeDesc(NodeExt.Flags);
ReferingNode:=TCodeTreeNode(NodeExt.Data);
if (ReferingType=ctnProcedure) then begin
// procedure alias => check if it is an 'external' procedure
if (ReferingNode=nil) or (ReferingNode.Desc<>ctnProcedure)
or (not ProcNodeHasSpecifier(ReferingNode,psEXTERNAL)) then
ReferingType:=ctnNone;
end;
if (not (ReferingType in [ctnTypeDefinition,ctnConstDefinition,ctnProcedure]))
or (DefNode.Desc=ReferingType) then begin
TreeOfCodeTreeNodeExt.FreeAndDelete(AVLNode);
end;
AVLNode:=NextAVLNode;
end;
// insert additional sections
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
DefNode:=NodeExt.Node;
ReferingType:=TCodeTreeNodeDesc(NodeExt.Flags);
ReferingNode:=TCodeTreeNode(NodeExt.Data);
//DebugLn(['TCodeCompletionCodeTool.FixAliasDefinitions Old=',DefNode.DescAsString,' New=',NodeDescToStr(ReferingType)]);
// check in front
if ReferingType in [ctnTypeDefinition,ctnConstDefinition] then begin
case ReferingType of
ctnTypeDefinition: NewSection:='type';
ctnConstDefinition: NewSection:='const';
ctnProcedure: NewSection:=''; // Changed from NewSrc to NewSection. Is it correct? Juha
else NewSection:='bug';
end;
if DefNode.PriorBrother=nil then begin
// this is the start of the section
MoveCursorToNodeStart(DefNode.Parent);
ReadNextAtom;
if not SourceChangeCache.Replace(gtNone,gtNone,
CurPos.StartPos,CurPos.EndPos,NewSection) then exit;
end else begin
// this is not the start of the section
ReferingNodeInFront:=FindReferingNodeExt(DefNode.PriorBrother);
if (ReferingNodeInFront=nil)
or (TCodeTreeNodeDesc(ReferingNodeInFront.Flags)<>ReferingType) then
begin
// the node in front has a different section
FromPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.StartPos);
if not SourceChangeCache.Replace(gtEmptyLine,gtNewLine,
FromPos,FromPos,NewSection) then exit;
end;
end;
end else if ReferingType=ctnProcedure then begin
// alias to an external procedure
// => replace alias with complete external procedure header
if DefNode.PriorBrother=nil then begin
// this is the start of the section
FromPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.Parent.StartPos);
ToPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.StartPos);
if not SourceChangeCache.Replace(gtNone,gtNone,
FromPos,ToPos,'') then exit;
end;
NewSrc:=ExtractProcHead(ReferingNode,[phpWithStart,phpWithVarModifiers,
phpWithParameterNames,phpWithDefaultValues,phpWithResultType,
phpWithOfObject,phpWithCallingSpecs,phpWithProcModifiers]);
OldProcName:=ExtractProcName(ReferingNode,[]);
FromPos:=System.Pos(OldProcName,NewSrc);
if DefNode.Desc in [ctnTypeDefinition,ctnConstDefinition] then
ProcName:=ExtractDefinitionName(DefNode)
else if DefNode.Desc=ctnProcedure then
ProcName:=ExtractProcName(DefNode,[])
else
ProcName:=NodeExt.Txt;
NewSrc:=copy(NewSrc,1,FromPos-1)+ProcName
+copy(NewSrc,FromPos+length(OldProcName),length(NewSrc));
FromPos:=DefNode.StartPos;
ToPos:=DefNode.EndPos;
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewSrc)
then
exit;
end;
// check behind
if DefNode.NextBrother=nil then begin
// this is the end of the section
end else begin
// this is not the end of the section
ReferingNodeBehind:=FindReferingNodeExt(DefNode.NextBrother);
if ReferingNodeBehind<>nil then begin
// the next node will change the section
end else begin
// the next node should stay in the same type of section
case DefNode.NextBrother.Desc of
ctnTypeDefinition: NewSrc:='type';
ctnConstDefinition: NewSrc:='const';
else NewSrc:='';
end;
if NewSrc<>'' then begin
FromPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.NextBrother.StartPos);
if not SourceChangeCache.Replace(gtEmptyLine,gtNewLine,
FromPos,FromPos,NewSrc) then exit;
end;
end;
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=SourceChangeCache.Apply;
end;
function TCodeCompletionCodeTool.FindConstFunctions(
out TreeOfCodeTreeNodeExt: TAVLTree): boolean;
{ find public dummy functions that can be replaced with a constant
For example:
function MPI_CONVERSION_FN_NULL : PMPI_Datarep_conversion_function;
begin
MPI_CONVERSION_FN_NULL:=PMPI_Datarep_conversion_function(0);
end;
Where the expression only contains unit defined types, constants,
variables, built-in const functions and no members nor functions.
NodeExt.Txt: description
NodeExt.Node: definition node
NodeExt.Data: function body node
NodeExt.ExtTxt1: ExtractCode(ExprStart,ExprEnd,[]);
}
var
Definitions: TAVLTree;
function FindProcWithName(Identifier: PChar): TCodeTreeNodeExtension;
begin
Result:=FindCodeTreeNodeExtWithIdentifier(Definitions,Identifier);
end;
procedure CheckProcNode(ProcNode: TCodeTreeNode);
// check if node is a function (not class function)
var
Node: TCodeTreeNode;
FuncName: String;
ExprStart: LongInt;
NodeText: String;
NodeExt: TCodeTreeNodeExtension;
ExprEnd: LongInt;
ResultNodeExt: TCodeTreeNodeExtension;
function CheckExprIdentifier(Identifier: PChar): boolean;
var
NodeExt: TCodeTreeNodeExtension;
NewPos: Integer;
AtomStart: integer;
begin
Result:=true;
if CompareIdentifiers('Result',Identifier)=0 then exit;
if CompareIdentifiers(PChar(FuncName),Identifier)=0 then exit;
// check for const and type definitions
NodeExt:=FindCodeTreeNodeExt(Definitions,GetIdentifier(Identifier));
if NodeExt=nil then
NodeExt:=FindProcWithName(Identifier);
if (NodeExt<>nil) and (NodeExt.Node<>nil) then begin
if NodeExt.Node.Desc in [ctnConstDefinition,ctnTypeDefinition] then
exit;
if (NodeExt.Node.Desc=ctnProcedure) and IsPCharInSrc(Identifier) then
begin
// read atom behind identifier name
NewPos:=PtrInt({%H-}PtrUInt(Identifier))-PtrInt({%H-}PtrUInt(@Src[1]))+1;
inc(NewPos,GetIdentLen(Identifier));
ReadRawNextPascalAtom(Src,NewPos,AtomStart,Scanner.NestedComments,true);
if (AtomStart<=SrcLen) and (Src[AtomStart]<>'(') then begin
// no parameters
// this is the function pointer, not the result => constant
exit;
end;
end;
end;
// check for compiler built in operators, constants and types
if IsWordBuiltInFunc.DoItCaseInsensitive(Identifier) then exit;
if WordIsBinaryOperator.DoItCaseInsensitive(Identifier) then exit;
if WordIsPredefinedFPCIdentifier.DoItCaseInsensitive(Identifier) then exit;
Result:=false;
end;
begin
if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then exit;
//DebugLn(['CheckProcNode START ',ExtractProcHead(ProcNode,[])]);
MoveCursorToNodeStart(ProcNode);
// read 'function'
ReadNextAtom;
if not UpAtomIs('FUNCTION') then exit;
// read name
ReadNextAtom;
FuncName:=GetAtom;
ReadNextAtom;
if CurPos.Flag=cafRoundBracketOpen then begin
// skip optional empty parameter list ()
ReadNextAtom;
if CurPos.Flag<>cafRoundBracketClose then exit;
ReadNextAtom;
end;
// read :
if CurPos.Flag<>cafColon then exit;
// read result type
ReadNextAtom;
if not AtomIsIdentifier then exit;
// check if there is a public definition of the procedure
NodeText:=GetRedefinitionNodeText(ProcNode);
if TreeOfCodeTreeNodeExt<>nil then begin
ResultNodeExt:=FindCodeTreeNodeExt(TreeOfCodeTreeNodeExt,NodeText);
if ResultNodeExt<>nil then begin
DebugLn(['CheckProcNode function exists twice']);
exit;
end;
end;
NodeExt:=FindCodeTreeNodeExt(Definitions,NodeText);
if (NodeExt=nil) or (NodeExt.Node=nil) or (NodeExt.Node.Desc<>ctnProcedure)
then begin
DebugLn(['CheckProcNode function is not public NodeText=',NodeText]);
exit;
end;
// check child nodes only contain the proc head and a begin block
Node:=ProcNode.FirstChild;
if Node=nil then exit;
if Node.Desc=ctnProcedureHead then begin
Node:=Node.NextBrother;
if Node=nil then exit;
end;
if Node.Desc<>ctnBeginBlock then exit;
//DebugLn(['CheckProcNode has begin block']);
// check begin block is only a single assignment
MoveCursorToNodeStart(Node);
// read begin
ReadNextAtom;
// read 'Result' or 'FunctionName'
ReadNextAtom;
if (not UpAtomIs('RESULT')) and (not AtomIs(FuncName)) then exit;
// read :=
ReadNextAtom;
if not UpAtomIs(':=') then exit;
// read expression
ReadNextAtom;
ExprStart:=CurPos.StartPos;
ExprEnd:=ExprStart;
while (CurPos.EndPos<=Node.EndPos) do begin
if (CurPos.Flag in [cafSemicolon,cafEnd]) then
break;
// check if all identifiers can be used in a constant expression
if AtomIsIdentifier
and not CheckExprIdentifier(@Src[CurPos.StartPos]) then
exit;
ExprEnd:=CurPos.EndPos;
ReadNextAtom;
end;
if ExprStart=ExprEnd then exit;
//DebugLn(['CheckProcNode FOUND']);
// save values
ResultNodeExt:=TCodeTreeNodeExtension.Create;
ResultNodeExt.Txt:=NodeText;
ResultNodeExt.Node:=NodeExt.Node;
ResultNodeExt.Data:=ProcNode;
ResultNodeExt.ExtTxt1:=ExtractCode(ExprStart,ExprEnd,[]);
if TreeOfCodeTreeNodeExt=nil then
TreeOfCodeTreeNodeExt:=TAVLTree.Create(@CompareCodeTreeNodeExt);
TreeOfCodeTreeNodeExt.Add(ResultNodeExt);
end;
var
Node: TCodeTreeNode;
begin
Result:=false;
TreeOfCodeTreeNodeExt:=nil;
try
BuildTree(lsrImplementationStart);
// first step: find all unit identifiers (excluding implementation section)
if not GatherUnitDefinitions(Definitions,true,true) then exit;
//DebugLn(['TCodeCompletionCodeTool.FindConstFunctions ',Src]);
// now check all functions
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnInterface, ctnUsesSection, ctnBeginBlock, ctnAsmBlock, ctnProcedureHead,
ctnTypeSection, ctnConstSection, ctnVarSection, ctnResStrSection:
Node:=Node.NextSkipChilds;
ctnProcedure:
begin
CheckProcNode(Node);
Node:=Node.NextSkipChilds;
end;
else
Node:=Node.Next;
end;
end;
finally
DisposeAVLTree(Definitions);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.ReplaceConstFunctions(
TreeOfCodeTreeNodeExt: TAVLTree; SourceChangeCache: TSourceChangeCache
): boolean;
{ replaces public dummy functions with a constant.
The function body will be removed.
See the function FindConstFunctions.
}
function IsConstSectionNeeded(Node: TCodeTreeNode): boolean;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
if Node.PriorBrother.Desc=ctnConstSection then exit(false);
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node=Node.PriorBrother then begin
// the function in front will be replaced too
exit(false);
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=true;
end;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
DefNode: TCodeTreeNode;
BodyNode: TCodeTreeNode;
Expr: String;
FromPos: LongInt;
ToPos: LongInt;
NewSrc: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if SourceChangeCache=nil then exit;
if (TreeOfCodeTreeNodeExt=nil) or (TreeOfCodeTreeNodeExt.Count=0) then
exit(true);
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
DebugLn(['TCodeCompletionCodeTool.ReplaceConstFunctions ',NodeExt.Txt]);
DefNode:=NodeExt.Node;
BodyNode:=TCodeTreeNode(NodeExt.Data);
Expr:=NodeExt.ExtTxt1;
DebugLn(['TCodeCompletionCodeTool.ReplaceConstFunctions Expr=',Expr]);
// remove body node
FromPos:=FindLineEndOrCodeInFrontOfPosition(BodyNode.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(BodyNode.EndPos);
if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13]) then begin
inc(ToPos);
if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13])
and (Src[ToPos-1]<>Src[ToPos]) then
inc(ToPos);
end;
DebugLn(['TCodeCompletionCodeTool.ReplaceConstFunctions Body="',copy(Src,FromPos,ToPos-FromPos),'"']);
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
// replace definition
FromPos:=DefNode.StartPos;
ToPos:=DefNode.EndPos;
if Src[ToPos]=';' then inc(ToPos);// add semicolon
NewSrc:=Beauty.GetIndentStr(Beauty.Indent)
+ExtractProcName(DefNode,[])+' = '+Expr+';';
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewSrc);
// add 'const' keyword
if IsConstSectionNeeded(DefNode) then begin
FromPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.StartPos);
SourceChangeCache.Replace(gtEmptyLine,gtNewLine,FromPos,FromPos,'const');
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=SourceChangeCache.Apply;
end;
function TCodeCompletionCodeTool.FindTypeCastFunctions(out
TreeOfCodeTreeNodeExt: TAVLTree): boolean;
{ find public dummy functions that can be replaced with a type
For example:
function PMPI_Win_f2c(win : longint) : MPI_Win;
begin
PMPI_Win_f2c:=MPI_Win(win);
end;
Where the expression is Result := ResultType(Parameter).
NodeExt.Txt: description
NodeExt.Node: definition node
NodeExt.Data: function body node
NodeExt.ExtTxt1: ResultType
}
var
Definitions: TAVLTree;
procedure CheckProcNode(ProcNode: TCodeTreeNode);
// check if node is a function (not class function)
var
Node: TCodeTreeNode;
FuncName: PChar;
NodeText: String;
NodeExt: TCodeTreeNodeExtension;
ResultNodeExt: TCodeTreeNodeExtension;
ParamName: PChar;
ResultType: PChar;
begin
if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then exit;
//DebugLn(['CheckProcNode START ',ExtractProcHead(ProcNode,[])]);
MoveCursorToNodeStart(ProcNode);
ReadNextAtom;
// read 'function'
if not UpAtomIs('FUNCTION') then exit;
ReadNextAtom;
// read name
if CurPos.Flag<>cafWord then exit;
FuncName:=@Src[CurPos.StartPos];
ReadNextAtom;
// read (
if CurPos.Flag<>cafRoundBracketOpen then exit;
ReadNextAtom;
// read optional const
if UpAtomIs('CONST') then
ReadNextAtom;
// read parameter name
if CurPos.Flag<>cafWord then exit;
ParamName:=@Src[CurPos.StartPos];
ReadNextAtom;
// read :
if CurPos.Flag<>cafColon then exit;
ReadNextAtom;
// read parameter type
if CurPos.Flag<>cafWord then exit;
ReadNextAtom;
// read )
if CurPos.Flag<>cafRoundBracketClose then exit;
ReadNextAtom;
// read :
if CurPos.Flag<>cafColon then exit;
// read result type
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
ResultType:=@Src[CurPos.StartPos];
// check if there is a public definition of the procedure
NodeText:=GetRedefinitionNodeText(ProcNode);
if TreeOfCodeTreeNodeExt<>nil then begin
ResultNodeExt:=FindCodeTreeNodeExt(TreeOfCodeTreeNodeExt,NodeText);
if ResultNodeExt<>nil then begin
DebugLn(['CheckProcNode function exists twice']);
exit;
end;
end;
NodeExt:=FindCodeTreeNodeExt(Definitions,NodeText);
if (NodeExt=nil) or (NodeExt.Node=nil) or (NodeExt.Node.Desc<>ctnProcedure)
then begin
DebugLn(['CheckProcNode function is not public NodeText=',NodeText]);
exit;
end;
// check child nodes only contain the proc head and a begin block
Node:=ProcNode.FirstChild;
if Node=nil then exit;
if Node.Desc=ctnProcedureHead then begin
Node:=Node.NextBrother;
if Node=nil then exit;
end;
if Node.Desc<>ctnBeginBlock then exit;
//DebugLn(['CheckProcNode has begin block']);
// check begin block is only a single assignment
MoveCursorToNodeStart(Node);
// read begin
ReadNextAtom;
// read 'Result' or 'FunctionName'
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
if (not UpAtomIs('RESULT'))
and (CompareIdentifiers(FuncName,@Src[CurPos.StartPos])<>0) then exit;
// read :=
ReadNextAtom;
if not UpAtomIs(':=') then exit;
// read type cast to result type
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
if (CompareIdentifiers(ResultType,@Src[CurPos.StartPos])<>0) then exit;
// read (
ReadNextAtom;
if CurPos.Flag<>cafRoundBracketOpen then exit;
// read parameter
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
if (CompareIdentifiers(ParamName,@Src[CurPos.StartPos])<>0) then exit;
// read )
ReadNextAtom;
if CurPos.Flag<>cafRoundBracketClose then exit;
//DebugLn(['CheckProcNode FOUND']);
// save values
ResultNodeExt:=TCodeTreeNodeExtension.Create;
ResultNodeExt.Txt:=NodeText;
ResultNodeExt.Node:=NodeExt.Node;
ResultNodeExt.Data:=ProcNode;
ResultNodeExt.ExtTxt1:=GetIdentifier(ResultType);
if TreeOfCodeTreeNodeExt=nil then
TreeOfCodeTreeNodeExt:=TAVLTree.Create(@CompareCodeTreeNodeExt);
TreeOfCodeTreeNodeExt.Add(ResultNodeExt);
end;
var
Node: TCodeTreeNode;
begin
Result:=false;
TreeOfCodeTreeNodeExt:=nil;
try
BuildTree(lsrImplementationStart);
// first step: find all unit identifiers (excluding implementation section)
if not GatherUnitDefinitions(Definitions,true,true) then exit;
// now check all functions
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnInterface, ctnUsesSection, ctnBeginBlock, ctnAsmBlock, ctnProcedureHead,
ctnTypeSection, ctnConstSection, ctnVarSection, ctnResStrSection:
Node:=Node.NextSkipChilds;
ctnProcedure:
begin
CheckProcNode(Node);
Node:=Node.NextSkipChilds;
end;
else
Node:=Node.Next;
end;
end;
finally
DisposeAVLTree(Definitions);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.ReplaceTypeCastFunctions(
TreeOfCodeTreeNodeExt: TAVLTree; SourceChangeCache: TSourceChangeCache
): boolean;
{ replaces public dummy functions with a type.
The function body will be removed.
See the function FindTypeCastFunctions.
}
function IsTypeSectionNeeded(Node: TCodeTreeNode): boolean;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
if Node.PriorBrother.Desc=ctnTypeSection then exit(false);
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node=Node.PriorBrother then begin
// the function in front will be replaced too
exit(false);
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=true;
end;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
DefNode: TCodeTreeNode;
BodyNode: TCodeTreeNode;
Expr: String;
FromPos: LongInt;
ToPos: LongInt;
NewSrc: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if SourceChangeCache=nil then exit;
if (TreeOfCodeTreeNodeExt=nil) or (TreeOfCodeTreeNodeExt.Count=0) then
exit(true);
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
AVLNode:=TreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
DebugLn(['TCodeCompletionCodeTool.ReplaceTypeCastFunctions ',NodeExt.Txt]);
DefNode:=NodeExt.Node;
BodyNode:=TCodeTreeNode(NodeExt.Data);
Expr:=NodeExt.ExtTxt1;
DebugLn(['TCodeCompletionCodeTool.ReplaceTypeCastFunctions Expr=',Expr]);
// remove body node
FromPos:=FindLineEndOrCodeInFrontOfPosition(BodyNode.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(BodyNode.EndPos);
if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13]) then begin
inc(ToPos);
if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13])
and (Src[ToPos-1]<>Src[ToPos]) then
inc(ToPos);
end;
DebugLn(['TCodeCompletionCodeTool.ReplaceTypeCastFunctions Body="',copy(Src,FromPos,ToPos-FromPos),'"']);
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
// replace definition
FromPos:=DefNode.StartPos;
ToPos:=DefNode.EndPos;
if Src[ToPos]=';' then inc(ToPos);// add semicolon
NewSrc:=Beauty.GetIndentStr(Beauty.Indent)
+ExtractProcName(DefNode,[])+' = '+Expr+';';
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewSrc);
// add 'type' keyword
if IsTypeSectionNeeded(DefNode) then begin
FromPos:=FindLineEndOrCodeInFrontOfPosition(DefNode.StartPos);
SourceChangeCache.Replace(gtEmptyLine,gtNewLine,FromPos,FromPos,'type');
end;
AVLNode:=TreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=SourceChangeCache.Apply;
end;
function TCodeCompletionCodeTool.MovePointerTypesToTargetSections(
SourceChangeCache: TSourceChangeCache): boolean;
const
NodeMovedFlag = 1;
var
NodeMoves: TCodeGraph;// an edge means, move the FromNode in front of the ToNode
Beauty: TBeautifyCodeOptions;
procedure InitNodeMoves;
begin
if NodeMoves=nil then
NodeMoves:=TCodeGraph.Create;
end;
procedure ClearNodeMoves;
begin
FreeAndNil(NodeMoves);
end;
procedure AddMove(Node, InsertInFrontOf: TCodeTreeNode);
begin
if Node=InsertInFrontOf then exit;
if Node=nil then RaiseException(20170421201640,'inconsistency');
if InsertInFrontOf=nil then RaiseException(20170421201643,'inconsistency');
NodeMoves.AddEdge(Node,InsertInFrontOf);
end;
function WholeSectionIsMoved(SectionNode: TCodeTreeNode): boolean;
var
Node: TCodeTreeNode;
GraphNode: TCodeGraphNode;
begin
Node:=SectionNode.FirstChild;
while Node<>nil do begin
GraphNode:=NodeMoves.GetGraphNode(Node,false);
if (GraphNode=nil) or (GraphNode.OutTreeCount=0) then
exit(false);
Node:=Node.NextBrother;
end;
Result:=true;
end;
function ApplyNodeMove(GraphNode: TCodeGraphNode; MoveNode: boolean;
InsertPos, Indent: integer): boolean;
// if MoveNode=true then move code of GraphNode.Node to InsertPos
// Always: move recursively all nodes that should be moved to GraphNode too
var
AVLNode: TAVLTreeNode;
GraphEdge: TCodeGraphEdge;
Node: TCodeTreeNode;
FromPos: LongInt;
ToPos: LongInt;
NodeSrc: String;
begin
Result:=false;
Node:=GraphNode.Node;
// marked as moved
GraphNode.Flags:=NodeMovedFlag;
DebugLn(['ApplyNodeMoves ',ExtractNode(Node,[])]);
if MoveNode then begin
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.EndPos);
NodeSrc:=Beauty.GetIndentStr(Indent)+Trim(copy(Src,FromPos,ToPos-FromPos));
// remove
if (Node.PriorBrother=nil)
and (Node.Parent<>nil) and (Node.Parent.Desc in AllDefinitionSections)
and WholeSectionIsMoved(Node.Parent)
then begin
// the whole section is moved and this is the first node of the section
// remove the section header too
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.Parent.StartPos);
end;
DebugLn(['ApplyNodeMove Remove: "',copy(Src,FromPos,ToPos-FromPos),'"']);
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
// insert
DebugLn(['ApplyNodeMove Insert: "',NodeSrc,'"']);
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
InsertPos,InsertPos,NodeSrc) then exit;
end;
// move dependent nodes
if GraphNode.InTree<>nil then begin
AVLNode:=GraphNode.InTree.FindLowest;
while AVLNode<>nil do begin
GraphEdge:=TCodeGraphEdge(AVLNode.Data);
if not ApplyNodeMove(GraphEdge.FromNode,true,InsertPos,Indent) then exit;
AVLNode:=GraphNode.InTree.FindSuccessor(AVLNode);
end;
end;
Result:=true;
end;
function ApplyNodeMoves(ExceptionOnCircle: boolean): boolean;
var
GraphEdge: TCodeGraphEdge;
ListOfGraphNodes: TFPList;
i: Integer;
GraphNode: TCodeGraphNode;
InsertPos: LongInt;
Indent: LongInt;
begin
Result:=false;
if NodeMoves.Edges.Count=0 then exit(true);
// check that every node has no more than one destination
GraphNode:=NodeMoves.FindGraphNodeWithNumberOfOutEdges(2,-1);
if GraphNode<>nil then begin
DebugLn(['TCodeCompletionCodeTool.MovePointerTypesToTargetSections.ApplyNodeMoves inconsistency: node should be moved to several places: ',ExtractNode(GraphNode.Node,[])]);
raise Exception.Create('TCodeCompletionCodeTool.MovePointerTypesToTargetSections.ApplyNodeMoves node should be moved to several places');
end;
// sort topologically and break all circles
repeat
GraphEdge:=NodeMoves.GetTopologicalSortedList(ListOfGraphNodes,true,false,true);
if GraphEdge=nil then break;
if ExceptionOnCircle then
raise Exception.Create('TCodeCompletionCodeTool.MovePointerTypesToTargetSections.ApplyNodeMoves found circle: From='+ExtractNode(GraphEdge.FromNode.Node,[])+' To='+ExtractNode(GraphEdge.ToNode.Node,[]));
DebugLn(['TCodeCompletionCodeTool.MovePointerTypesToTargetSections.ApplyNodeMoves break circle: From=',ExtractNode(GraphEdge.FromNode.Node,[]),' To=',ExtractNode(GraphEdge.ToNode.Node,[])]);
NodeMoves.DeleteEdge(GraphEdge);
ListOfGraphNodes.Free;
until false;
for i:=0 to ListOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(ListOfGraphNodes[i]);
DebugLn(['ApplyNodeMoves i=',i,' ',ExtractNode(GraphNode.Node,[]),' InFrontCnt=',GraphNode.InTreeCount,' BehindCnt=',GraphNode.OutTreeCount]);
end;
{ apply changes
the ListOfGraphNodes is sorted topologically with nodes at end must be
moved first
For example:
var AnArray: array[0..EndValue] of char;
const EndValue = TMyInteger(1);
type TMyInteger = longint;
Edges: TMyInteger -> AnArray
EndValue -> AnArray
List:
}
NodeMoves.ClearNodeFlags;
for i:=ListOfGraphNodes.Count-1 downto 0 do begin
GraphNode:=TCodeGraphNode(ListOfGraphNodes[i]);
if GraphNode.Flags=0 then begin
InsertPos:=FindLineEndOrCodeInFrontOfPosition(GraphNode.Node.StartPos);
Indent:=Beauty.GetLineIndent(Src,GraphNode.Node.StartPos);
if not ApplyNodeMove(GraphNode,false,InsertPos,Indent) then exit;
end;
end;
Result:=SourceChangeCache.Apply;
end;
var
Definitions: TAVLTree;// tree of TCodeTreeNodeExtension
Graph: TCodeGraph;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
Node: TCodeTreeNode;
GraphNode: TCodeGraphNode;
RequiredAVLNode: TAVLTreeNode;
GraphEdge: TCodeGraphEdge;
RequiredNode: TCodeTreeNode;
RequiredTypeNode: TCodeTreeNode;
begin
Result:=false;
if (SourceChangeCache=nil) or (Scanner=nil) then exit;
NodeMoves:=nil;
Definitions:=nil;
Graph:=nil;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
try
// move the pointer types to the same type sections
if not BuildUnitDefinitionGraph(Definitions,Graph,false) then exit;
SourceChangeCache.MainScanner:=Scanner;
if Definitions=nil then exit(true);
InitNodeMoves;
AVLNode:=Definitions.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
Node:=NodeExt.Node;
if (Node.Desc=ctnTypeDefinition) and (Node.FirstChild<>nil)
and (Node.FirstChild.Desc=ctnPointerType) then begin
// this is a pointer type
// check if it only depends on the type nodes of a single section
//DebugLn(['MovePointerTypesToTargetSections Pointer=',ExtractNode(Node,[])]);
RequiredTypeNode:=nil;
GraphNode:=Graph.GetGraphNode(Node,false);
if GraphNode.OutTree<>nil then begin
RequiredAVLNode:=GraphNode.OutTree.FindLowest;
while RequiredAVLNode<>nil do begin
GraphEdge:=TCodeGraphEdge(RequiredAVLNode.Data);
RequiredNode:=GraphEdge.ToNode.Node;
if (RequiredNode.Desc=ctnTypeDefinition)
and (RequiredNode.Parent.Desc=ctnTypeSection) then begin
//DebugLn(['MovePointerTypesToTargetSections required=',ExtractNode(RequiredNode,[])]);
if RequiredTypeNode=nil then begin
RequiredTypeNode:=RequiredNode;
end
else if RequiredTypeNode.Parent<>RequiredNode.Parent then begin
DebugLn(['MovePointerTypesToTargetSections required nodes in different type sections']);
RequiredTypeNode:=nil;
break;
end;
end else begin
DebugLn(['MovePointerTypesToTargetSections required nodes are not only types']);
RequiredTypeNode:=nil;
break;
end;
RequiredAVLNode:=GraphNode.OutTree.FindSuccessor(RequiredAVLNode);
end;
end;
if (RequiredTypeNode<>nil) then begin
// this pointer type depends only on the type nodes of a single type
// section
if (Node.Parent<>RequiredNode.Parent) then begin
// pointer type is in other section => move
DebugLn(['MovePointerTypesToTargetSections move Pointer=',ExtractNode(Node,[]),' Required=',ExtractNode(RequiredNode,[])]);
AddMove(Node,RequiredNode);
end;
end;
end;
AVLNode:=Definitions.FindSuccessor(AVLNode);
end;
Result:=ApplyNodeMoves(false);
finally
DisposeAVLTree(Definitions);
Graph.Free;
ClearNodeMoves;
end;
end;
function TCodeCompletionCodeTool.FixForwardDefinitions(
SourceChangeCache: TSourceChangeCache): boolean;
function UpdateGraph(var Definitions: TAVLTree; var Graph: TCodeGraph;
Rebuild: boolean): boolean;
begin
if Definitions<>nil then begin
DisposeAVLTree(Definitions);
end;
if Graph<>nil then begin
Graph.Free;
Graph:=nil;
end;
if Rebuild then
Result:=BuildUnitDefinitionGraph(Definitions,Graph,true)
else
Result:=true;
end;
function CreateTypeSectionForCycle(CycleOfGraphNodes: TFPList;
var Definitions: TAVLTree; var Graph: TCodeGraph): boolean;
// CycleOfGraphNodes is a list of TCodeGraphNode that should be moved
// to a new type section
function IndexOfNode(Node: TCodeTreeNode): integer;
begin
Result:=CycleOfGraphNodes.Count-1;
while (Result>=0)
and (TCodeGraphNode(CycleOfGraphNodes[Result]).Node<>Node) do
dec(Result);
end;
var
i: Integer;
GraphNode: TCodeGraphNode;
Node: TCodeTreeNode;
NewTxt: String;
EndGap: TGapTyp;
InsertPos: LongInt;
Indent: LongInt;
FromPos: LongInt;
ToPos: LongInt;
Beauty: TBeautifyCodeOptions;
begin
// check if whole type sections are moved and combine them
i:=CycleOfGraphNodes.Count-1;
while i>=0 do begin
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[i]);
Node:=GraphNode.Node;
if Node.Parent.Desc=ctnTypeSection then begin
if IndexOfNode(Node.Parent)>=0 then begin
// the whole type section of this type will be moved
// => remove this type
CycleOfGraphNodes.Delete(i);
end else begin
// check if all types of this type section will be moved
Node:=Node.Parent.FirstChild;
while (Node<>nil) and (IndexOfNode(Node)>=0) do
Node:=Node.NextBrother;
if Node=nil then begin
// all types of this type section will be moved
// => remove the type and add the type section instead
CycleOfGraphNodes.Delete(i);
CycleOfGraphNodes.Add(Graph.AddGraphNode(GraphNode.Node.Parent));
end;
end;
end;
dec(i);
end;
// create new type section
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// Note: InsertPos must be outside the types and type sections which are moved
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[0]);
Node:=GraphNode.Node;
if Node.Parent.Desc=ctnTypeSection then
Node:=Node.Parent;
InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
Indent:=Beauty.GetLineIndent(Src,Node.StartPos);
SourceChangeCache.Replace(gtEmptyLine,gtNewLine,InsertPos,InsertPos,
Beauty.GetIndentStr(Indent)+'type');
inc(Indent,Beauty.Indent);
// move the types
for i:=0 to CycleOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[i]);
Node:=GraphNode.Node;
if i=CycleOfGraphNodes.Count-1 then
EndGap:=gtEmptyLine
else
EndGap:=gtNewLine;
if Node.Desc=ctnTypeSection then begin
// remove type section
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.EndPos,true);
DebugLn(['CreateTypeSectionForCircle Removing type section: ',ExtractCode(FromPos,ToPos,[])]);
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
// add all types of type section to new type section
if Node.FirstChild<>nil then begin
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.FirstChild.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.LastChild.EndPos);
NewTxt:=Beauty.GetIndentStr(Indent)+ExtractCode(FromPos,ToPos,[phpWithComments]);
DebugLn(['CreateTypeSectionForCircle Adding types: ',NewTxt]);
SourceChangeCache.Replace(gtNewLine,EndGap,InsertPos,InsertPos,NewTxt);
end;
end else if Node.Desc in [ctnTypeDefinition,ctnGenericType] then begin
// remove type
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.EndPos);
DebugLn(['CreateTypeSectionForCircle Removing node: ',ExtractCode(FromPos,ToPos,[])]);
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
// add type to new type section
NewTxt:=Beauty.GetIndentStr(Indent)+ExtractNode(Node,[phpWithComments]);
DebugLn(['CreateTypeSectionForCircle Adding type: ',NewTxt]);
SourceChangeCache.Replace(gtNewLine,EndGap,InsertPos,InsertPos,NewTxt);
end else
raise Exception.Create('inconsistency');
end;
// apply changes
Result:=SourceChangeCache.Apply;
if not Result then exit;
// rebuild graph
Result:=UpdateGraph(Definitions,Graph,true);
end;
function FixCycle(var Definitions: TAVLTree;
var Graph: TCodeGraph; CircleNode: TCodeGraphNode): boolean;
var
CycleOfGraphNodes: TFPList; // list of TCodeGraphNode
procedure RaiseCanNotFixCircle(const Msg: string);
var
i: Integer;
GraphNode: TCodeGraphNode;
s: String;
begin
DebugLn(['RaiseCanNotFixCircle Msg="',Msg,'"']);
s:='Can not auto fix a circle in definitions: '+Msg;
for i:=0 to CycleOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[i]);
DebugLn([' ',i,': ',GetRedefinitionNodeText(GraphNode.Node)]);
end;
raise Exception.Create(s);
end;
var
i: Integer;
GraphNode: TCodeGraphNode;
ParentNode: TCodeTreeNode;
Node: TCodeTreeNode;
NeedsMoving: Boolean;
begin
Result:=false;
CycleOfGraphNodes:=nil;
try
// get all nodes of this CycleOfGraphNodes
Graph.GetMaximumCircle(CircleNode,CycleOfGraphNodes);
// check if all nodes are types
for i:=0 to CycleOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[i]);
if not (GraphNode.Node.Desc in [ctnTypeDefinition,ctnGenericType])
then begin
RaiseCanNotFixCircle('Only types can build circles, not '+GraphNode.Node.DescAsString);
end;
end;
NeedsMoving:=false;
// check if the whole type CycleOfGraphNodes has one parent
ParentNode:=TCodeGraphNode(CycleOfGraphNodes[0]).Node.Parent;
for i:=1 to CycleOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(CycleOfGraphNodes[i]);
if GraphNode.Node.Parent<>ParentNode then begin
DebugLn(['FixCycle cycle is not yet in one type section -> needs moving']);
NeedsMoving:=true;
break;
end;
end;
// check if the parent only contains the CycleOfGraphNodes nodes
if not NeedsMoving then begin
Node:=ParentNode.FirstChild;
while Node<>nil do begin
i:=CycleOfGraphNodes.Count-1;
while (i>=0) and (TCodeGraphNode(CycleOfGraphNodes[i]).Node<>Node) do dec(i);
if i<0 then begin
DebugLn(['FixCycle cycle has not yet its own type section -> needs moving']);
NeedsMoving:=true;
break;
end;
Node:=Node.NextBrother;
end;
end;
if NeedsMoving then begin
DebugLn(['TCodeCompletionCodeTool.FixForwardDefinitions.FixCycle moving types into one type section']);
Result:=CreateTypeSectionForCycle(CycleOfGraphNodes,Definitions,Graph);
exit;
end else begin
// remove definitions nodes and use the type section instead
DebugLn(['FixCycle already ok']);
Graph.CombineNodes(CycleOfGraphNodes,Graph.GetGraphNode(ParentNode,true));
end;
finally
CycleOfGraphNodes.Free;
end;
Result:=true;
end;
function BreakCycles(var Definitions: TAVLTree;
var Graph: TCodeGraph): boolean;
var
ListOfGraphNodes: TFPList;
CycleEdge: TCodeGraphEdge;
begin
Result:=false;
ListOfGraphNodes:=nil;
try
Graph.DeleteSelfCircles;
repeat
//WriteCodeGraphDebugReport(Graph);
CycleEdge:=Graph.GetTopologicalSortedList(ListOfGraphNodes,true,false,false);
if CycleEdge=nil then break;
DebugLn(['FixForwardDefinitions.CheckCircles Circle found containing ',
GetRedefinitionNodeText(CycleEdge.FromNode.Node),
' and ',
GetRedefinitionNodeText(CycleEdge.ToNode.Node)]);
if not FixCycle(Definitions,Graph,CycleEdge.FromNode) then exit;
until false;
finally
ListOfGraphNodes.Free;
end;
Result:=true;
end;
function MoveNodes(TreeOfNodeMoveEdges: TAVLTree): boolean;
// TreeOfNodeMoveEdges is a tree of TNodeMoveEdge
// it is sorted for insert position (i.e. left node must be inserted
// in front of right node)
function NodeWillBeMoved(Node: TCodeTreeNode): boolean;
var
AVLNode: TAVLTreeNode;
CurMove: TNodeMoveEdge;
GraphNode: TCodeGraphNode;
begin
AVLNode:=TreeOfNodeMoveEdges.FindLowest;
while AVLNode<>nil do begin
CurMove:=TNodeMoveEdge(AVLNode.Data);
GraphNode:=CurMove.GraphNode;
if GraphNode.Node=Node then exit(true);
AVLNode:=TreeOfNodeMoveEdges.FindSuccessor(AVLNode);
end;
Result:=false;
end;
function GetFirstVarDefSequenceNode(Node: TCodeTreeNode): TCodeTreeNode;
begin
while (Node.PriorBrother<>nil) and (Node.PriorBrother.FirstChild=nil) do
Node:=Node.PriorBrother;
Result:=Node;
end;
function GetLastVarDefSequenceNode(Node: TCodeTreeNode): TCodeTreeNode;
begin
Result:=nil;
while (Node<>nil) do begin
Result:=Node;
if (Node.FirstChild<>nil) then break;
Node:=Node.NextBrother;
end;
end;
function WholeVarDefSequenceWillBeMoved(Node: TCodeTreeNode): boolean;
// test, if all variable definitions of a sequence will be moved
// example: var a,b,c: integer;
begin
Node:=GetFirstVarDefSequenceNode(Node);
while (Node<>nil) do begin
if not NodeWillBeMoved(Node) then exit(false);
if (Node.FirstChild<>nil) then break;// this is the last of the sequence
Node:=Node.NextBrother;
end;
Result:=true;
end;
function WholeSectionWillBeMoved(Node: TCodeTreeNode): boolean;
// test, if all child nodes will be moved
begin
Node:=Node.FirstChild;
while (Node<>nil) do begin
if not NodeWillBeMoved(Node) then exit(false);
Node:=Node.NextBrother;
end;
Result:=true;
end;
var
AVLNode: TAVLTreeNode;
CurMove: TNodeMoveEdge;
GraphNode: TCodeGraphNode;// move what
PosGraphNode: TCodeGraphNode;// move where (in front of)
Node: TCodeTreeNode;
FromPos: LongInt;
ToPos: LongInt;
DestNode: TCodeTreeNode;
NextAVLNode: TAVLTreeNode;
NextMove: TNodeMoveEdge;
NextGraphNode: TCodeGraphNode;// move what next
NextPosGraphNode: TCodeGraphNode;// move where next (in front of)
NextInsertAtSamePos: boolean;
NeedSection: TCodeTreeNodeDesc;
LastSection: TCodeTreeNodeDesc;
LastInsertAtSamePos: boolean;
InsertPos: LongInt;
Indent: LongInt;
DestSection: TCodeTreeNodeDesc;
NewTxt: String;
DestNodeInFront: TCodeTreeNode;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
AVLNode:=TreeOfNodeMoveEdges.FindLowest;
LastSection:=ctnNone;
LastInsertAtSamePos:=false;
DestNode:=nil;
DestSection:=ctnNone;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// process every move
while AVLNode<>nil do begin
CurMove:=TNodeMoveEdge(AVLNode.Data);
GraphNode:=CurMove.GraphNode;// move what
PosGraphNode:=TCodeGraphNode(GraphNode.Data);// move where (in front of)
NextAVLNode:=TreeOfNodeMoveEdges.FindSuccessor(AVLNode);
if NextAVLNode<>nil then begin
NextMove:=TNodeMoveEdge(NextAVLNode.Data);
NextGraphNode:=NextMove.GraphNode;// move what next
NextPosGraphNode:=TCodeGraphNode(NextGraphNode.Data);// move where next
NextInsertAtSamePos:=NextPosGraphNode=PosGraphNode;
end else begin
NextInsertAtSamePos:=false;
end;
DebugLn(['MoveNodes: move ',
GetRedefinitionNodeText(GraphNode.Node),' ',CleanPosToStr(GraphNode.Node.StartPos),
' (TopoLvl=',CurMove.TologicalLevel,')',
' in front of ',GetRedefinitionNodeText(PosGraphNode.Node),' ',CleanPosToStr(PosGraphNode.Node.StartPos)
]);
Node:=GraphNode.Node;
DestNode:=PosGraphNode.Node;
// remove node
if (Node.Parent<>nil)
and (Node.Parent.Desc in AllDefinitionSections)
and WholeSectionWillBeMoved(Node.Parent) then begin
// the whole type/var/const section will be moved
if Node.PriorBrother=nil then begin
// this is the first node of the section
// => remove the whole section
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.Parent.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.Parent.EndPos,true);
end else begin
// this is not the first node of the section
// => remove nothing
FromPos:=0;
ToPos:=0;
end;
end
else if Node.Desc=ctnVarDefinition then begin
// removing a variable definition can be tricky, because for example
// var a,b,c: integer;
if Node.FirstChild<>nil then begin
// this is the last of a sequence
if WholeVarDefSequenceWillBeMoved(Node) then begin
// the whole variable definition will be moved
// and this is the last of the sequence
// => remove the whole definition (names and type)
FromPos:=FindLineEndOrCodeInFrontOfPosition(
GetFirstVarDefSequenceNode(Node).StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(
GetLastVarDefSequenceNode(Node).EndPos,true);
end else if NodeWillBeMoved(Node.PriorBrother) then begin
// this is for example: var a,b,c: integer
// and only b and c will be moved. The b, plus the space behind was
// already marked for removal
// => remove the c and the space behind
FromPos:=Node.StartPos;
MoveCursorToNodeStart(Node);
ReadNextAtom;// read identifier
AtomIsIdentifierE;
ToPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos,true);
end else begin
// this is for example: var a,b: integer
// and only b will be moved.
// => remove ,b plus the space behind
MoveCursorToNodeStart(Node.PriorBrother);
ReadNextAtom;// read identifier
AtomIsIdentifierE;
ReadNextAtom;// read comma
if not AtomIsChar(',') then RaiseCharExpectedButAtomFound(20170421201647,',');
FromPos:=CurPos.StartPos;
ReadNextAtom;// read identifier
AtomIsIdentifierE;
ReadNextAtom;//read colon
if not AtomIsChar(':') then RaiseCharExpectedButAtomFound(20170421201651,':');
ToPos:=CurPos.StartPos;
end;
end else begin
// this is not the last of a sequence
if WholeVarDefSequenceWillBeMoved(Node) then begin
// the whole sequence will be moved. This is done by the last node.
// => nothing to do
FromPos:=0;
ToPos:=0;
end else begin
// remove the b,
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
MoveCursorToNodeStart(Node);
ReadNextAtom;// read identifier
AtomIsIdentifierE;
ReadNextAtom;// read comma
if not AtomIsChar(',') then RaiseCharExpectedButAtomFound(20170421201654,',');
ToPos:=CurPos.StartPos;
end;
end;
end else begin
// remove the whole node
FromPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Node.EndPos);
end;
if ToPos>FromPos then begin
DebugLn(['MoveNodes remove "',ExtractCode(FromPos,ToPos,[]),'"']);
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then
exit;
end;
// find needed section type
if Node.Desc in AllIdentifierDefinitions then
NeedSection:=Node.Parent.Desc
else
NeedSection:=ctnNone;
// find insert position
if not LastInsertAtSamePos then begin
//DebugLn(['MoveNodes LastInsertAtSamePos=false, compute destination ...']);
if (DestNode.Desc in AllIdentifierDefinitions) then begin
DestNode:=GetFirstVarDefSequenceNode(DestNode);
DestSection:=DestNode.Parent.Desc;
if DestNode.PriorBrother<>nil then begin
// the destination is in front of a definition, but in the middle
// of a section
// example: type a=char; | b=byte;
// => insert in front of destination
//DebugLn(['MoveNodes destination is middle of a section. Node in front=',GetRedefinitionNodeText(DestNode.PriorBrother)]);
end else begin
// the destination is the first node of a section
// example: type | t=char;
if NeedSection=DestSection then begin
// insertion needs the same section type
// => insert in front of destination
end else begin
// insertion needs another section type
// => insert in front of the section
DestNode:=DestNode.Parent;
end;
//DebugLn(['MoveNodes destination is first node of a section ']);
end;
end else begin
// the destination is not in a section
// example: in front of a type section
// => insert in front of destination
// find the section in front
DestNodeInFront:=DestNode.PriorBrother;
while (DestNodeInFront<>nil) and NodeWillBeMoved(DestNodeInFront) do
DestNodeInFront:=DestNodeInFront.PriorBrother;
if (DestNodeInFront<>nil)
and (DestNodeInFront.Desc in AllDefinitionSections) then
DestSection:=DestNodeInFront.Desc
else
DestSection:=ctnNone;
//DebugLn(['MoveNodes destination is not in a section']);
end;
InsertPos:=FindLineEndOrCodeAfterPosition(DestNode.StartPos);
Indent:=Beauty.GetLineIndent(Src,DestNode.StartPos);
//DebugLn(['MoveNodes DestNode=',GetRedefinitionNodeText(DestNode),':',DestNode.DescAsString,' DestSection=',NodeDescToStr(DestSection)]);
end;
// start a new section if needed
//DebugLn(['MoveNodes LastInsertAtSamePos=',LastInsertAtSamePos,' NeedSection=',NodeDescToStr(NeedSection),' LastSection=',NodeDescToStr(LastSection),' DestSection=',NodeDescToStr(DestSection)]);
if (LastInsertAtSamePos and (NeedSection<>LastSection))
or ((not LastInsertAtSamePos) and (NeedSection<>DestSection)) then begin
// start a new section
case NeedSection of
ctnVarSection: NewTxt:='var';
ctnConstSection: NewTxt:='const';
ctnResStrSection: NewTxt:='resourcestring';
ctnTypeSection: NewTxt:='type';
ctnLabelSection: NewTxt:='label';
else NewTxt:='';
end;
if NewTxt<>'' then begin
DebugLn(['MoveNodes start new section: insert "',NewTxt,'"']);
if not SourceChangeCache.Replace(gtEmptyLine,gtNewLine,
InsertPos,InsertPos,NewTxt)
then
exit;
Indent:=Beauty.Indent;
end;
end;
// insert node
if Node.Desc=ctnVarDefinition then begin
NewTxt:=GetIdentifier(@Src[Node.StartPos]);
MoveCursorToNodeStart(GetLastVarDefSequenceNode(Node));
ReadNextAtom;
AtomIsIdentifierE;
ReadNextAtom;
if not AtomIsChar(':') then RaiseCharExpectedButAtomFound(20170421201657,':');
FromPos:=CurPos.StartPos;
ToPos:=Node.EndPos;
NewTxt:=NewTxt+ExtractCode(FromPos,ToPos,[phpWithComments]);
end else begin
FromPos:=Node.StartPos;
ToPos:=FindLineEndOrCodeAfterPosition(Node.EndPos);
NewTxt:=ExtractCode(FromPos,ToPos,[phpWithComments]);
end;
NewTxt:=Beauty.GetIndentStr(Indent)+NewTxt;
DebugLn(['MoveNodes insert "',NewTxt,'"']);
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
NewTxt) then exit;
// restore destination section if needed
if not NextInsertAtSamePos then begin
// this was the last insertion at this destination
DebugLn(['MoveNodes this was the last insertion at this dest NeedSection=',NodeDescToStr(NeedSection),' DestSection=',NodeDescToStr(DestSection)]);
if (DestNode.Desc in AllIdentifierDefinitions)
and (NeedSection<>DestSection)
and (DestSection in AllDefinitionSections) then begin
// restore the section of destination
case DestSection of
ctnVarSection: NewTxt:='var';
ctnConstSection: NewTxt:='const';
ctnResStrSection: NewTxt:='resourcestring';
ctnTypeSection: NewTxt:='type';
ctnLabelSection: NewTxt:='label';
else NewTxt:='';
end;
if NewTxt<>'' then begin
DebugLn(['MoveNodes restore destination section: insert "',NewTxt,'"']);
if not SourceChangeCache.Replace(gtEmptyLine,gtNewLine,
InsertPos,InsertPos,NewTxt)
then
exit;
end;
end;
end;
LastSection:=NeedSection;
LastInsertAtSamePos:=NextInsertAtSamePos;
AVLNode:=NextAVLNode;
end;
Result:=SourceChangeCache.Apply;
end;
function CheckOrder(var Definitions: TAVLTree;
var Graph: TCodeGraph): boolean;
// sort definitions topologically in source
// the Graph must be acyclic
var
ListOfGraphNodes: TFPList;
CircleEdge: TCodeGraphEdge;
i: Integer;
GraphNode: TCodeGraphNode;
AVLNode: TAVLTreeNode;
UsedByGraphNode: TCodeGraphNode;
PosGraphNode: TCodeGraphNode;
PosUsedByGraphNode: TCodeGraphNode;
NodeMoveEdges: TAVLTree;
NewMoveEdge: TNodeMoveEdge;
begin
Result:=false;
ListOfGraphNodes:=nil;
NodeMoveEdges:=TAVLTree.Create(@CompareNodeMoveEdges);
try
//WriteCodeGraphDebugReport(Graph);
// create a topologically sorted list
CircleEdge:=Graph.GetTopologicalSortedList(ListOfGraphNodes,false,true,false);
if CircleEdge<>nil then
raise Exception.Create('not acyclic');
{ set the GraphNode.Data to those GraphNodes leaves
with the lowest Node.StartPos
For example:
var AnArray: array[0..EndValue] of char;
const EndValue = TMyInteger(1);
type TMyInteger = integer;
EndValue must be moved in front of AnArray
and TMyInteger must be moved in front of EndValue and AnArray.
The topological list gives:
TMyInteger
EndValue
AnArray
NOTE: topological order alone can not be used,
because unrelated definitions will be mixed somehow.
}
// init the destinations
for i:=0 to ListOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(ListOfGraphNodes[i]);
//DebugLn(['CheckOrder ',GetRedefinitionNodeText(GraphNode.Node)]);
GraphNode.Data:=GraphNode;
end;
// calculate the destinations as minimum of all dependencies
for i:=ListOfGraphNodes.Count-1 downto 0 do begin
GraphNode:=TCodeGraphNode(ListOfGraphNodes[i]);
if GraphNode.InTree<>nil then begin
AVLNode:=GraphNode.InTree.FindLowest;
while AVLNode<>nil do begin
UsedByGraphNode:=TCodeGraphEdge(AVLNode.Data).FromNode;
// for example: type TMyPointer = TMyInteger;
// GraphNode.Node is TMyInteger
// UsedByGraphNode.Node is TMyPointer
//DebugLn(['CheckOrder GraphNode=',GetRedefinitionNodeText(GraphNode.Node),' UsedBy=',GetRedefinitionNodeText(UsedByGraphNode.Node)]);
PosGraphNode:=TCodeGraphNode(GraphNode.Data);
PosUsedByGraphNode:=TCodeGraphNode(UsedByGraphNode.Data);
if PosGraphNode.Node.StartPos>PosUsedByGraphNode.Node.StartPos then
GraphNode.Data:=PosUsedByGraphNode;
AVLNode:=GraphNode.InTree.FindSuccessor(AVLNode);
end;
end;
end;
// create the list of moves
// sorted for: 1. destination position,
// 2. topological level,
// 3. origin position in source
for i:=0 to ListOfGraphNodes.Count-1 do begin
GraphNode:=TCodeGraphNode(ListOfGraphNodes[i]);
PosGraphNode:=TCodeGraphNode(GraphNode.Data);
if GraphNode<>PosGraphNode then begin
DebugLn(['CheckOrder Move: ',
GetRedefinitionNodeText(GraphNode.Node),' ',CleanPosToStr(GraphNode.Node.StartPos),
' TopoLvl=',GraphNode.Flags,
' in front of ',GetRedefinitionNodeText(PosGraphNode.Node),' ',CleanPosToStr(PosGraphNode.Node.StartPos)
]);
NewMoveEdge:=TNodeMoveEdge.Create;
NewMoveEdge.GraphNode:=GraphNode;
NewMoveEdge.DestPos:=PosGraphNode.Node.StartPos;
NewMoveEdge.TologicalLevel:=GraphNode.Flags;
NewMoveEdge.SrcPos:=GraphNode.Node.StartPos;
NodeMoveEdges.Add(NewMoveEdge);
end;
end;
Result:=MoveNodes(NodeMoveEdges);
// ToDo: maybe need UpdateGraph?
if Definitions<>nil then ;
finally
DisposeAVLTree(NodeMoveEdges);
ListOfGraphNodes.Free;
end;
end;
var
Definitions: TAVLTree;
Graph: TCodeGraph;
begin
Result:=false;
if (SourceChangeCache=nil) or (Scanner=nil) then begin
DebugLn(['TCodeCompletionCodeTool.FixForwardDefinitions no scanner']);
exit;
end;
Definitions:=nil;
Graph:=nil;
try
// Workaround:
// move the pointer types to the same type sections
//if not MovePointerTypesToTargetSections(SourceChangeCache) then exit;
//exit(true);
if not BuildUnitDefinitionGraph(Definitions,Graph,true) then begin
DebugLn(['TCodeCompletionCodeTool.FixForwardDefinitions BuildUnitDefinitionGraph failed']);
exit;
end;
if Graph=nil then begin
// no definitions found
exit(true);
end;
SourceChangeCache.MainScanner:=Scanner;
// fix cycles
if not BreakCycles(Definitions,Graph) then begin
DebugLn(['TCodeCompletionCodeTool.FixForwardDefinitions CheckCircles failed']);
exit;
end;
// now the graph is acyclic and nodes can be moved
if not CheckOrder(Definitions,Graph) then begin
DebugLn(['TCodeCompletionCodeTool.FixForwardDefinitions CheckOrder failed']);
exit;
end;
finally
UpdateGraph(Definitions,Graph,false);
end;
Result:=true;
end;
function TCodeCompletionCodeTool.GatherUnitDefinitions(out
TreeOfCodeTreeNodeExt: TAVLTree;
OnlyInterface, ExceptionOnRedefinition: boolean): boolean;
procedure RaiseRedefinition(Node1, Node2: TCodeTreeNode);
begin
MoveCursorToNodeStart(Node1);
RaiseException(20170421201704,'redefinition found: '+GetRedefinitionNodeText(Node1)
+' at '+CleanPosToStr(Node1.StartPos)
+' and at '+CleanPosToStr(Node2.StartPos));
end;
procedure AddDefinition(Node: TCodeTreeNode);
var
NodeExt: TCodeTreeNodeExtension;
NodeText: String;
begin
NodeText:=GetRedefinitionNodeText(Node);
NodeExt:=FindCodeTreeNodeExt(TreeOfCodeTreeNodeExt,NodeText);
if NodeExt<>nil then begin
if NodeIsForwardProc(NodeExt.Node)
and (not NodeIsForwardProc(Node)) then begin
// this is the procedure body of the forward definition -> skip
exit;
end;
if ExceptionOnRedefinition then
RaiseRedefinition(NodeExt.Node,Node);
end;
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Txt:=NodeText;
TreeOfCodeTreeNodeExt.Add(NodeExt);
NodeExt.Node:=Node;
end;
var
Node: TCodeTreeNode;
begin
Result:=false;
TreeOfCodeTreeNodeExt:=nil;
if OnlyInterface then
BuildTree(lsrImplementationStart)
else
BuildTree(lsrInitializationStart);
// find all unit identifiers (excluding sub types)
TreeOfCodeTreeNodeExt:=TAVLTree.Create(@CompareCodeTreeNodeExt);
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnProcedureHead, ctnParameterList, ctnInitialization, ctnFinalization,
ctnBeginBlock, ctnAsmBlock:
Node:=Node.NextSkipChilds;
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnEnumIdentifier,
ctnGenericType:
begin
// add or update definition
AddDefinition(Node);
if (Node.Desc=ctnTypeDefinition)
and (Node.FirstChild<>nil)
and (Node.FirstChild.Desc=ctnEnumerationType) then
Node:=Node.FirstChild
else
Node:=Node.NextSkipChilds;
end;
ctnProcedure:
begin
AddDefinition(Node);
Node:=Node.NextSkipChilds;
end;
else
if OnlyInterface and (Node.Desc=ctnImplementation) then
break;
Node:=Node.Next;
end;
end;
Result:=true;
end;
function TCodeCompletionCodeTool.BuildUnitDefinitionGraph(out
DefinitionsTreeOfCodeTreeNodeExt: TAVLTree; out Graph: TCodeGraph;
OnlyInterface: boolean): boolean;
procedure CheckRange(Node: TCodeTreeNode; FromPos, ToPos: integer);
// search the range for defined identifiers
// and add edges to graph
var
Identifier: PChar;
NodeExt: TCodeTreeNodeExtension;
begin
if (FromPos>=ToPos) or (FromPos<1) then exit;
//DebugLn(['CheckRange Range="',dbgstr(Src[FromPos..ToPos-1]),'"']);
MoveCursorToCleanPos(FromPos);
repeat
ReadNextAtom;
if (CurPos.StartPos>=ToPos) or (CurPos.StartPos>SrcLen) then break;
if AtomIsIdentifier then begin
Identifier:=@Src[CurPos.StartPos];
NodeExt:=FindCodeTreeNodeExtWithIdentifier(
DefinitionsTreeOfCodeTreeNodeExt,
Identifier);
if NodeExt<>nil then begin
if Graph=nil then
Graph:=TCodeGraph.Create;
//if Graph.GetEdge(Node,NodeExt.Node,false)=nil then
// DebugLn(['CheckRange AddEdge: ',GetRedefinitionNodeText(Node),' uses ',GetRedefinitionNodeText(NodeExt.Node)]);
Graph.AddEdge(Node,NodeExt.Node);
end;
end;
until false;
end;
procedure CheckSubNode(Node, SubNode: TCodeTreeNode);
var
ProcHead: TCodeTreeNode;
ParamList: TCodeTreeNode;
ChildNode: TCodeTreeNode;
FunctionResult: TCodeTreeNode;
begin
//DebugLn(['CheckSubNode ',GetRedefinitionNodeText(Node),' ',GetRedefinitionNodeText(SubNode)]);
case SubNode.Desc of
ctnTypeDefinition,ctnVarDefinition,ctnGenericType,ctnConstDefinition:
begin
ChildNode:=FindTypeNodeOfDefinition(SubNode);
if ChildNode<>nil then begin
CheckSubNode(Node,ChildNode);
end else if SubNode.Desc=ctnConstDefinition then begin
CheckRange(Node,ChildNode.StartPos,SubNode.EndPos);
end;
end;
ctnProcedure:
begin
BuildSubTreeForProcHead(SubNode,FunctionResult);
ProcHead:=SubNode.FirstChild;
ParamList:=ProcHead.FirstChild;
if ParamList<>nil then begin
ChildNode:=ParamList.FirstChild;
while ChildNode<>nil do begin
if (ChildNode.Desc=ctnVarDefinition) and (ChildNode.FirstChild<>nil)
then begin
CheckRange(Node,ChildNode.FirstChild.StartPos,ChildNode.EndPos);
end;
ChildNode:=ChildNode.NextBrother;
end;
end;
if FunctionResult<>nil then begin
CheckRange(Node,FunctionResult.StartPos,
FunctionResult.StartPos
+GetIdentLen(@Src[FunctionResult.StartPos]));
end;
end;
ctnClassInterface, ctnDispinterface, ctnClass, ctnObject, ctnRecordType,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass:
begin
ChildNode:=SubNode.FirstChild;
while (ChildNode<>nil) and (ChildNode.HasAsParent(SubNode)) do begin
if ChildNode.Desc in AllIdentifierDefinitions then begin
CheckSubNode(Node,ChildNode);
ChildNode:=ChildNode.NextSkipChilds;
end else
ChildNode:=ChildNode.Next;
end;
end;
else
CheckRange(Node,SubNode.StartPos,SubNode.Parent.EndPos);
end;
end;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
Node: TCodeTreeNode;
begin
Result:=false;
DefinitionsTreeOfCodeTreeNodeExt:=nil;
Graph:=nil;
if not GatherUnitDefinitions(DefinitionsTreeOfCodeTreeNodeExt,OnlyInterface,true) then
begin
DebugLn(['TCodeCompletionCodeTool.BuildUnitDefinitionGraph GatherUnitDefinitions failed']);
exit;
end;
if DefinitionsTreeOfCodeTreeNodeExt=nil then exit(true);
AVLNode:=DefinitionsTreeOfCodeTreeNodeExt.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
Node:=NodeExt.Node;
CheckSubNode(Node,Node);
AVLNode:=DefinitionsTreeOfCodeTreeNodeExt.FindSuccessor(AVLNode);
end;
Result:=true;
end;
procedure TCodeCompletionCodeTool.WriteCodeGraphDebugReport(Graph: TCodeGraph);
function NodeToStr(Node: TCodeTreeNode): string;
begin
case Node.Desc of
ctnProcedure:
Result:=ExtractProcHead(Node,[phpInUpperCase,phpWithoutSemicolon]);
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnEnumIdentifier,
ctnGenericType:
Result:=ExtractDefinitionName(Node);
else
Result:=Node.DescAsString;
end;
Result:=Result+'{'+CleanPosToStr(Node.StartPos)+'}';
end;
var
AVLNode: TAVLTreeNode;
GraphNode: TCodeGraphNode;
Node: TCodeTreeNode;
Cnt: LongInt;
EdgeAVLNode: TAVLTreeNode;
Edge: TCodeGraphEdge;
begin
DebugLn(['TCodeCompletionCodeTool.WriteCodeGraphDebugReport ',DbgSName(Graph),
' NodeCount=',Graph.Nodes.Count,
' EdgeCount=',Graph.Edges.Count]);
Graph.ConsistencyCheck;
AVLNode:=Graph.Nodes.FindLowest;
while AVLNode<>nil do begin
GraphNode:=TCodeGraphNode(AVLNode.Data);
Node:=GraphNode.Node;
DebugLn([' ',NodeToStr(Node),' needs ',GraphNode.OutTreeCount,' definitions, is used by ',GraphNode.InTreeCount,' definitions.']);
if GraphNode.OutTreeCount>0 then begin
DbgOut(' Needs:');
EdgeAVLNode:=GraphNode.OutTree.FindLowest;
Cnt:=0;
while EdgeAVLNode<>nil do begin
inc(Cnt);
if Cnt=5 then begin
DbgOut(' ...');
break;
end;
Edge:=TCodeGraphEdge(EdgeAVLNode.Data);
DbgOut(' '+NodeToStr(Edge.ToNode.Node));
EdgeAVLNode:=GraphNode.OutTree.FindSuccessor(EdgeAVLNode);
end;
DebugLn;
end;
if GraphNode.InTreeCount>0 then begin
DbgOut(' Used by:');
EdgeAVLNode:=GraphNode.InTree.FindLowest;
Cnt:=0;
while EdgeAVLNode<>nil do begin
inc(Cnt);
if Cnt=5 then begin
DbgOut(' ...');
break;
end;
Edge:=TCodeGraphEdge(EdgeAVLNode.Data);
DbgOut(' '+NodeToStr(Edge.FromNode.Node));
EdgeAVLNode:=GraphNode.InTree.FindSuccessor(EdgeAVLNode);
end;
DebugLn;
end;
AVLNode:=Graph.Nodes.FindSuccessor(AVLNode);
end;
end;
function TCodeCompletionCodeTool.FindEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string; const Sections: TPascalClassSections;
ListOfPCodeXYPosition: TFPList; out AllEmpty: boolean): boolean;
var
ProcBodyNodes: TAVLTree;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
Caret: TCodeXYPosition;
CaretP: PCodeXYPosition;
begin
Result:=false;
ProcBodyNodes:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
Result:=FindEmptyMethods(CursorPos,AClassName,Sections,ProcBodyNodes,AllEmpty);
if Result then begin
AVLNode:=ProcBodyNodes.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if CleanPosToCaret(NodeExt.Node.StartPos,Caret) then begin
New(CaretP);
CaretP^:=Caret;
ListOfPCodeXYPosition.Add(CaretP);
end;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
end;
end;
finally
DisposeAVLTree(ProcBodyNodes);
end;
end;
function TCodeCompletionCodeTool.FindEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string; const Sections: TPascalClassSections;
CodeTreeNodeExtensions: TAVLTree;
out AllEmpty: boolean): boolean;
// NodeExt.Node is the body node
// NodeExt.Data is the definition node
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
TypeSectionNode: TCodeTreeNode;
ProcBodyNodes, ClassProcs: TAVLTree;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
NextAVLNode: TAVLTreeNode;
DefAVLNode: TAVLTreeNode;
DefNodeExt: TCodeTreeNodeExtension;
Desc: TCodeTreeNodeDesc;
Fits: Boolean;
s: TPascalClassSection;
procedure GatherClassProcs;
begin
// gather existing proc definitions in the class
if ClassProcs=nil then begin
ClassProcs:=GatherProcNodes(FCompletingFirstEntryNode,
[phpInUpperCase,phpAddClassName],
ExtractClassName(CodeCompleteClassNode,true));
end;
end;
begin
Result:=false;
AllEmpty:=false;
if (AClassName<>'') and (CursorPos.Y<1) then begin
BuildTree(lsrInitializationStart);
CursorNode:=FindClassNodeInInterface(AClassName,true,false,true);
CodeCompleteClassNode:=CursorNode;
end else begin
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
CodeCompleteClassNode:=FindClassNode(CursorNode);
end;
if CodeCompleteClassNode=nil then begin
DebugLn(['TCodeCompletionCodeTool.FindEmptyMethods no class at ',Dbgs(CursorPos)]);
exit;
end;
ProcBodyNodes:=nil;
ClassProcs:=nil;
try
// gather body nodes
TypeSectionNode:=CodeCompleteClassNode.GetTopMostNodeOfType(ctnTypeSection);
ProcBodyNodes:=GatherProcNodes(TypeSectionNode,
[phpInUpperCase,phpIgnoreForwards,phpOnlyWithClassname],
ExtractClassName(CodeCompleteClassNode,true));
// collect all empty bodies
AVLNode:=ProcBodyNodes.FindLowest;
while AVLNode<>nil do begin
NextAVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
//DebugLn(['TCodeCompletionCodeTool.FindEmptyMethods ',NodeExt.Txt,' ',ProcBodyIsEmpty(NodeExt.Node)]);
// check if proc body is empty (no code, no comments)
if ProcBodyIsEmpty(NodeExt.Node) then begin
GatherClassProcs;
// search the corresponding node in the class
DefAVLNode:=ClassProcs.Find(NodeExt);
if (DefAVLNode<>nil) then begin
DefNodeExt:=TCodeTreeNodeExtension(DefAVLNode.Data);
// check visibility section
if (DefNodeExt.Node.Parent<>nil) then begin
Desc:=DefNodeExt.Node.Parent.Desc;
Fits:=false;
for s:=Low(TPascalClassSection) to High(TPascalClassSection) do
if (s in Sections) and (PascalClassSectionToNodeDesc[s]=Desc) then
Fits:=true;
if Fits then begin
// empty and right section => add to tree
ProcBodyNodes.Delete(AVLNode);
NodeExt.Data:=DefNodeExt.Node;
CodeTreeNodeExtensions.Add(NodeExt);
end;
end;
end;
end;
AVLNode:=NextAVLNode;
end;
AllEmpty:=ProcBodyNodes.Count=0;
Result:=true;
finally
DisposeAVLTree(ClassProcs);
DisposeAVLTree(ProcBodyNodes);
end;
end;
function TCodeCompletionCodeTool.RemoveEmptyMethods(CursorPos: TCodeXYPosition;
const AClassName: string; const Sections: TPascalClassSections;
SourceChangeCache: TSourceChangeCache;
out AllRemoved: boolean;
const Attr: TProcHeadAttributes; out RemovedProcHeads: TStrings): boolean;
var
ProcBodyNodes: TAVLTree;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
FirstNodeExt: TCodeTreeNodeExtension;
LastNodeExt: TCodeTreeNodeExtension;
FromPos: LongInt;
ToPos: LongInt;
FirstGroup: Boolean;
CommentEndPos: integer;
CommentStartPos: integer;
ProcDefNodes: TAVLTree;
NextAVLNode: TAVLTreeNode;
ProcHead: String;
begin
Result:=false;
AllRemoved:=false;
RemovedProcHeads:=nil;
if (SourceChangeCache=nil) or (Scanner=nil) then exit;
SourceChangeCache.MainScanner:=Scanner;
ProcDefNodes:=nil;
ProcBodyNodes:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
Result:=FindEmptyMethods(CursorPos,AClassName,Sections,ProcBodyNodes,AllRemoved);
if Result and (ProcBodyNodes<>nil) and (ProcBodyNodes.Count>0) then begin
// sort the nodes for position
ProcBodyNodes.OnCompare:=@CompareCodeTreeNodeExtWithPos;
ProcDefNodes:=TAVLTree.Create(@CompareCodeTreeNodeExtWithPos);
// delete bodies
AVLNode:=ProcBodyNodes.FindLowest;
FirstGroup:=true;
while AVLNode<>nil do begin
// gather a group of continuous proc nodes
FirstNodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
LastNodeExt:=FirstNodeExt;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
while (AVLNode<>nil) do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node<>LastNodeExt.Node.NextBrother then break;
LastNodeExt:=NodeExt;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
end;
// delete group
FromPos:=FindLineEndOrCodeInFrontOfPosition(FirstNodeExt.Node.StartPos,true);
ToPos:=FindLineEndOrCodeAfterPosition(LastNodeExt.Node.EndPos,true);
{$IFDEF VerboseBug16168}
debugln(['TCodeCompletionCodeTool.RemoveEmptyMethods ',dbgstr(copy(Src,FromPos,ToPos-FromPos))]);
{$ENDIF}
if AllRemoved and FirstGroup
and FindClassMethodsComment(FromPos,CommentStartPos,CommentEndPos) then begin
// all method bodies will be removed => remove the default comment too
if FindNextNonSpace(Src,CommentEndPos)>=FromPos then begin
// the default comment is directly in front
// => remove it too
FromPos:=FindLineEndOrCodeInFrontOfPosition(CommentStartPos,true);
end;
end;
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then
exit;
FirstGroup:=false;
end;
// create the tree of proc definitions: ProcDefNodes
AVLNode:=ProcBodyNodes.FindLowest;
while AVLNode<>nil do begin
NextAVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
// remove NodeExt from ProcBodyNodes
ProcBodyNodes.Delete(AVLNode);
// and add it to ProcDefNodes
// the definition node is the Data
// Note: the class can contain errors and therefore some method bodies
// refer to the same definition => skip doubles
NodeExt.Node:=TCodeTreeNode(NodeExt.Data);
NodeExt.Position:=NodeExt.Node.StartPos;
if (NodeExt.Node<>nil) and (ProcDefNodes.Find(NodeExt)=nil) then begin
ProcDefNodes.Add(NodeExt);
if RemovedProcHeads=nil then
RemovedProcHeads:=TStringList.Create;
ProcHead:=ExtractProcHead(NodeExt.Node,Attr);
RemovedProcHeads.Add(ProcHead);
end else begin
NodeExt.Free;
end;
AVLNode:=NextAVLNode;
end;
// delete definitions
AVLNode:=ProcDefNodes.FindLowest;
while AVLNode<>nil do begin
// gather a group of continuous proc nodes
FirstNodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
LastNodeExt:=FirstNodeExt;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
while (AVLNode<>nil) do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node<>LastNodeExt.Node.NextBrother then break;
LastNodeExt:=NodeExt;
AVLNode:=ProcBodyNodes.FindSuccessor(AVLNode);
end;
// delete group
FromPos:=FindLineEndOrCodeInFrontOfPosition(FirstNodeExt.Node.StartPos,true);
ToPos:=FindLineEndOrCodeAfterPosition(LastNodeExt.Node.EndPos,true);
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then
exit;
end;
end;
Result:=SourceChangeCache.Apply;
finally
DisposeAVLTree(ProcBodyNodes);
DisposeAVLTree(ProcDefNodes);
end;
end;
function TCodeCompletionCodeTool.FindAssignMethod(CursorPos: TCodeXYPosition;
out ClassNode: TCodeTreeNode; out AssignDeclNode: TCodeTreeNode;
var MemberNodeExts: TAVLTree; out AssignBodyNode: TCodeTreeNode;
out InheritedDeclContext: TFindContext;
ProcName: string): boolean;
{ if CursorPos is in a class declaration search for a method "Assign"
and its corresponding body.
If CursorPos is in a method body use this as a Assign method and return
its corresponding declararion.
If neither return false.
Also return a tree of all variables and properties (excluding ancestors).
}
procedure SearchAssign(Tool: TFindDeclarationTool; Node: TCodeTreeNode;
var DeclNode: TCodeTreeNode);
var
Child: TCodeTreeNode;
CurProcName: String;
begin
if Node=nil then exit;
Child:=Node.FirstChild;
while Child<>nil do begin
if Child.Desc in AllClassSections then
SearchAssign(Tool,Child,DeclNode)
else if Child.Desc=ctnProcedure then begin
CurProcName:=Tool.ExtractProcName(Child,[]);
if CompareIdentifiers(PChar(CurProcName),PChar(ProcName))=0 then begin
if DeclNode<>nil then begin
debugln(['WARNING: TCodeCompletionCodeTool.FindAssignMethod.SearchAssign'
+' multiple ',ProcName,' methods found, using the first at ',CleanPosToStr(DeclNode.StartPos)]);
end else
DeclNode:=Child;
end;
end;
Child:=Child.NextBrother;
end;
end;
procedure GatherAssignableMembers(Node: TCodeTreeNode);
var
Child: TCodeTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
if Node=nil then exit;
Child:=Node.FirstChild;
while Child<>nil do begin
if Child.Desc in AllClassSections then
GatherAssignableMembers(Child)
else if (Child.Desc=ctnVarDefinition)
or ((Child.Desc=ctnProperty)
and (PropertyHasSpecifier(Child,'read'))
and (PropertyHasSpecifier(Child,'write')))
then begin
// a variable or a property which is readable and writable
if MemberNodeExts=nil then
MemberNodeExts:=TAVLTree.Create(@CompareCodeTreeNodeExtTxtAndPos);
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=Child;
NodeExt.Position:=Child.StartPos;
if Child.Desc=ctnVarDefinition then
NodeExt.Txt:=ExtractDefinitionName(Child)
else
NodeExt.Txt:=ExtractPropName(Child,false);
MemberNodeExts.Add(NodeExt);
end;
Child:=Child.NextBrother;
end;
end;
procedure FindVarsWrittenByProperties;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
WrittenNodeExt: TCodeTreeNodeExtension;
begin
if MemberNodeExts=nil then exit;
AVLNode:=MemberNodeExts.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Node.Desc=ctnProperty then begin
if PropertyHasSpecifier(NodeExt.Node,'write') then begin
ReadNextAtom;
if AtomIsIdentifier then begin
WrittenNodeExt:=FindCodeTreeNodeExtWithIdentifier(MemberNodeExts,
@Src[CurPos.StartPos]);
if WrittenNodeExt<>nil then
WrittenNodeExt.Data:=NodeExt.Node;
end;
end;
end;
AVLNode:=MemberNodeExts.FindSuccessor(AVLNode);
end;
end;
procedure FindInheritedAssign;
var
Params: TFindDeclarationParams;
begin
if ClassNode=nil then exit;
Params:=TFindDeclarationParams.Create(Self, ClassNode);
try
Params.Flags:=[fdfSearchInAncestors];
Params.Identifier:=PChar(ProcName);
if not FindIdentifierInContext(Params) then exit;
//debugln(['FindInheritedAssign NewNode=',Params.NewNode.DescAsString]);
if Params.NewNode=nil then exit;
if Params.NewNode.Desc<>ctnProcedure then exit;
InheritedDeclContext:=CreateFindContext(Params);
finally
Params.Free;
end;
end;
var
CleanPos: integer;
CursorNode: TCodeTreeNode;
Node: TCodeTreeNode;
begin
Result:=false;
ClassNode:=nil;
AssignDeclNode:=nil;
AssignBodyNode:=nil;
InheritedDeclContext:=CleanFindContext;
BuildTreeAndGetCleanPos(CursorPos,CleanPos);
if ProcName='' then ProcName:='Assign';
// check context
CursorNode:=FindDeepestNodeAtPos(CleanPos,true);
Node:=CursorNode;
while (Node<>nil) do begin
if (Node.Desc=ctnProcedure) then begin
if NodeIsMethodBody(Node) then begin
// cursor in method body
AssignBodyNode:=Node;
Result:=true;
AssignDeclNode:=FindCorrespondingProcNode(AssignBodyNode);
if AssignDeclNode<>nil then
ClassNode:=FindClassOrInterfaceNode(AssignDeclNode.Parent);
break;
end;
end else if (Node.Desc in AllClassObjects) then begin
// cursor in class/record
Result:=true;
ClassNode:=Node;
SearchAssign(Self,ClassNode,AssignDeclNode);
if AssignDeclNode<>nil then
AssignBodyNode:=FindCorrespondingProcNode(AssignDeclNode);
break;
end;
Node:=Node.Parent;
end;
if ClassNode=nil then exit;
GatherAssignableMembers(ClassNode);
FindVarsWrittenByProperties;
FindInheritedAssign;
end;
function TCodeCompletionCodeTool.AddAssignMethod(ClassNode: TCodeTreeNode;
MemberNodeExts: TFPList; const ProcName, ParamName, ParamType: string;
OverrideMod, CallInherited, CallInheritedOnlyInElse: boolean;
SourceChanger: TSourceChangeCache; out NewPos: TCodeXYPosition; out
NewTopLine, BlockTopLine, BlockBottomLine: integer; LocalVarName: string
): boolean;
var
NodeExt: TCodeTreeNodeExtension;
CleanDef: String;
Def: String;
aClassName: String;
ProcBody: String;
e: String;
SameType: boolean;
Indent: Integer;
IndentStep: LongInt;
SrcVar: String;
i: Integer;
Beauty: TBeautifyCodeOptions;
{$IFDEF EnableCodeCompleteTemplates}
NodeExtsStr: String;
{$ENDIF}
begin
Result:=false;
NewPos:=CleanCodeXYPosition;
NewTopLine:=-1;
if ClassNode=nil then exit;
if (ParamName='') or (ParamType='') then exit;
Beauty:=SourceChanger.BeautifyCodeOptions;
aClassName:=ExtractClassName(ClassNode,false);
CleanDef:=ProcName+'('+ParamType+');';
{$IFDEF EnableCodeCompleteTemplates}
if assigned(CTTemplateExpander)
and CTTemplateExpander.TemplateExists('AssignMethodDef') then
begin
Def := CTTemplateExpander.Expand('AssignMethodDef', '','', // Doesn't use linebreak or indentation
['ProcName', 'ParamName', 'ParamType', 'Override' ],
[ ProcName, ParamName, ParamType, OverrideMod ] );
end else
{$ENDIF EnableCodeCompleteTemplates}
begin
Def:='procedure '+ProcName+'('+ParamName+':'+ParamType+');';
if OverrideMod then Def:=Def+'override;';
end;
SrcVar:=ParamName;
// create the proc header
SameType:=CompareIdentifiers(PChar(aClassName),PChar(ParamType))=0;
e:=SourceChanger.BeautifyCodeOptions.LineEnd;
Indent:=0;
IndentStep:=SourceChanger.BeautifyCodeOptions.Indent;
{$IFDEF EnableCodeCompleteTemplates}
if assigned(CTTemplateExpander)
and CTTemplateExpander.TemplateExists('AssignMethod') then begin
if not SameType then begin
// add local variable
SrcVar:=LocalVarName;
if SrcVar='' then
SrcVar:='aSource';
if CompareIdentifiers(PChar(SrcVar),PChar(ParamName))=0 then begin
if CompareIdentifiers(PChar(SrcVar),'aSource')=0 then
SrcVar:='aSrc'
else
SrcVar:='aSource';
end;
end;
// add assignments
NodeExtsStr := '';
if MemberNodeExts<>nil then begin
for i:=0 to MemberNodeExts.Count-1 do
begin
NodeExt:=TCodeTreeNodeExtension(MemberNodeExts[i]);
NodeExtsStr := NodeExtsStr + NodeExt.Txt + '?';
end;
end;
ProcBody := CTTemplateExpander.Expand( 'AssignMethod',e,GetIndentStr(Indent),
['ClassName', 'ProcName', 'ParamName', 'ParamType',
'SameType', 'SrcVar', 'Inherited0', 'Inherited1',
'NodeExt' ],
[ aClassName, ProcName, ParamName, ParamType,
SameType, SrcVar,
CallInherited and (not CallInheritedOnlyInElse),
CallInherited and CallInheritedOnlyInElse,
NodeExtsStr ] );
end
else
{$ENDIF EnableCodeCompleteTemplates}
begin
ProcBody:='procedure '+aClassName+'.'+ProcName+'('+ParamName+':'+ParamType+');'+e;
if not SameType then begin
// add local variable
SrcVar:=LocalVarName;
if SrcVar='' then
SrcVar:='aSource';
if CompareIdentifiers(PChar(SrcVar),PChar(ParamName))=0 then begin
if CompareIdentifiers(PChar(SrcVar),'aSource')=0 then
SrcVar:='aSrc'
else
SrcVar:='aSource';
end;
ProcBody:=ProcBody+'var'+e
+Beauty.GetIndentStr(Indent+IndentStep)+SrcVar+':'+aClassName+';'+e;
end;
ProcBody:=ProcBody+'begin'+e;
inc(Indent,IndentStep);
// call inherited
if CallInherited and (not CallInheritedOnlyInElse) then
ProcBody:=ProcBody
+Beauty.GetIndentStr(Indent)+'inherited '+ProcName+'('+ParamName+');'+e;
if not SameType then begin
// add a parameter check to the new procedure
ProcBody:=ProcBody
+Beauty.GetIndentStr(Indent)+'if '+ParamName+' is '+aClassName+' then'+e
+Beauty.GetIndentStr(Indent)+'begin'+e;
inc(Indent,IndentStep);
ProcBody:=ProcBody+Beauty.GetIndentStr(Indent)+SrcVar+':='+aClassName+'('+ParamName+');'+e;
end;
// add assignments
if MemberNodeExts<>nil then begin
for i:=0 to MemberNodeExts.Count-1 do begin
NodeExt:=TCodeTreeNodeExtension(MemberNodeExts[i]);
// add assignment
ProcBody:=ProcBody+Beauty.GetIndentStr(Indent)+NodeExt.Txt+':='+SrcVar+'.'+NodeExt.Txt+';'+e;
end;
end;
if not SameType then begin
// close if block
dec(Indent,IndentStep);
if CallInherited and CallInheritedOnlyInElse then begin
ProcBody:=ProcBody+Beauty.GetIndentStr(Indent)+'end else'+e
+Beauty.GetIndentStr(Indent+IndentStep)+'inherited '+ProcName+'('+ParamName+');'+e;
end else begin
ProcBody:=ProcBody+Beauty.GetIndentStr(Indent)+'end;'+e
end;
end;
// close procedure body
ProcBody:=ProcBody+'end;';
end;
if not InitClassCompletion(ClassNode,SourceChanger) then exit;
ProcBody:=SourceChanger.BeautifyCodeOptions.BeautifyStatement(ProcBody,0);
AddClassInsertion(CleanDef,Def,ProcName,ncpPublicProcs,nil,ProcBody);
Result:=ApplyChangesAndJumpToFirstNewProc(ClassNode.StartPos,1,true,
NewPos,NewTopLine, BlockTopLine, BlockBottomLine);
end;
function TCodeCompletionCodeTool.AddAssignMethod(ClassNode: TCodeTreeNode;
MemberNodeExts: TFPList; const ProcName, ParamName, ParamType: string;
OverrideMod, CallInherited, CallInheritedOnlyInElse: boolean;
SourceChanger: TSourceChangeCache; out NewPos: TCodeXYPosition; out
NewTopLine: integer; LocalVarName: string): boolean;
var
BlockTopLine, BlockBottomLine: integer;
begin
Result := AddAssignMethod(ClassNode, MemberNodeExts, ProcName, ParamName, ParamType,
OverrideMod, CallInherited, CallInheritedOnlyInElse, SourceChanger, NewPos, NewTopLine,
BlockTopLine, BlockBottomLine, LocalVarName);
end;
function TCodeCompletionCodeTool.GetPossibleInitsForVariable(
CursorPos: TCodeXYPosition; out Statements: TStrings; out
InsertPositions: TObjectList; SourceChangeCache: TSourceChangeCache): boolean;
var
Identifier: PChar;
procedure AddStatement(aStatement: string);
begin
if SourceChangeCache<>nil then begin
SourceChangeCache.MainScanner:=Scanner;
SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(aStatement,0);
end;
{$IFDEF VerboseGetPossibleInitsForVariable}
debugln(['TCodeCompletionCodeTool.GetPossibleInitsForVariable.AddStatement "',aStatement,'"']);
{$ENDIF}
Statements.Add(aStatement);
end;
procedure AddAssignment(const aValue: string);
begin
AddStatement(GetIdentifier(Identifier)+':='+aValue+';');
end;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
IdentAtom: TAtomPosition;
Params: TFindDeclarationParams;
VarTool: TFindDeclarationTool;
VarNode: TCodeTreeNode;
ExprType: TExpressionType;
BeginNode: TCodeTreeNode;
InsertPosDesc: TInsertStatementPosDescription;
Node: TCodeTreeNode;
Tool: TFindDeclarationTool;
aContext: TFindContext;
FuncNode: TCodeTreeNode;
begin
{$IFDEF VerboseGetPossibleInitsForVariable}
debugln(['TCodeCompletionCodeTool.GetPossibleInitsForVariable ',dbgs(CursorPos)]);
{$ENDIF}
Result:=false;
Statements:=TStringList.Create;
InsertPositions:=TObjectList.create(true);
BuildTreeAndGetCleanPos(CursorPos, CleanCursorPos);
// find variable name
GetIdentStartEndAtPosition(Src,CleanCursorPos,
IdentAtom.StartPos,IdentAtom.EndPos);
{$IFDEF VerboseGetPossibleInitsForVariable}
debugln('TCodeCompletionCodeTool.GetPossibleInitsForLocalVar IdentAtom="',dbgstr(Src,IdentAtom.StartPos,IdentAtom.EndPos-IdentAtom.StartPos),'"');
{$ENDIF}
if IdentAtom.StartPos=IdentAtom.EndPos then exit;
// find context
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
// find declaration of identifier
VarTool:=nil;
VarNode:=nil;
Identifier:=@Src[IdentAtom.StartPos];
if (cmsResult in FLastCompilerModeSwitches)
and (CompareIdentifiers(Identifier,'Result')=0) then begin
FuncNode:=CursorNode;
while not NodeIsFunction(FuncNode) do
FuncNode:=FuncNode.Parent;
VarTool:=Self;
VarNode:=FuncNode;
Result:=true;
end;
if VarNode=nil then begin
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
Params.SetIdentifier(Self,Identifier,nil);
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFindVariable];
Result:=FindIdentifierInContext(Params);
VarTool:=Params.NewCodeTool;
VarNode:=Params.NewNode;
if (not Result) or (VarNode=nil) then begin
{$IFDEF VerboseGetPossibleInitsForVariable}
debugln(['TCodeCompletionCodeTool.GetPossibleInitsForVariable FindIdentifierInContext Result=',Result,' VarTool=',VarTool<>nil,' VarNode=',VarNode<>nil]);
{$ENDIF}
MoveCursorToAtomPos(IdentAtom);
RaiseException(20170421201708,'failed to resolve identifier "'+Identifier+'"');
end;
{$IFDEF VerboseGetPossibleInitsForVariable}
debugln(['TCodeCompletionCodeTool.GetPossibleInitsForVariable FindIdentifierInContext VarTool=',ExtractFilename(VarTool.MainFilename),' VarNode=',VarNode.DescAsString]);
{$ENDIF}
finally
Params.Free;
end;
end;
// resolve type
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
Params.Flags:=fdfDefaultForExpressions;
if VarNode.Desc in [ctnProcedure,ctnProcedureHead] then
Params.Flags:=Params.Flags+[fdfFunctionResult];
ExprType:=VarTool.ConvertNodeToExpressionType(VarNode,Params);
{$IFDEF VerboseGetPossibleInitsForVariable}
DebugLn('TCodeCompletionCodeTool.GetPossibleInitsForVariable ConvertNodeToExpressionType',
' Expr=',ExprTypeToString(ExprType));
{$ENDIF}
finally
Params.Free;
end;
case ExprType.Desc of
xtContext:
begin
// ToDo: ranges, records, objects, pointer, class, class of, interface
Node:=ExprType.Context.Node;
Tool:=ExprType.Context.Tool;
case Node.Desc of
ctnEnumerationType:
begin
// enumeration: add first 10 enums
Node:=Node.FirstChild;
while (Node<>nil) and (Statements.Count<10) do begin
if Node.Desc=ctnEnumIdentifier then
AddAssignment(GetIdentifier(@Tool.Src[Node.StartPos]));
Node:=Node.NextBrother;
end;
end;
ctnSetType:
// set of
AddAssignment('[]');
ctnClass,ctnClassInterface,ctnDispinterface,
ctnObjCClass,ctnObjCCategory,ctnObjCProtocol,ctnCPPClass:
AddAssignment('nil');
ctnPointerType:
AddAssignment('nil');
ctnProcedureType,ctnReferenceTo:
// address of proc
AddAssignment('nil');
ctnProcedureHead:
if Tool.NodeIsFunction(Node) then begin
Params:=TFindDeclarationParams.Create(Tool, Node);
try
aContext:=Tool.FindBaseTypeOfNode(Params,Node);
Tool:=aContext.Tool;
Node:=aContext.Node;
finally
Params.Free;
end;
end;
end;
end;
xtChar,
xtWideChar: begin AddAssignment('#0'); AddAssignment(''' '''); end;
xtReal,
xtSingle,
xtDouble,
xtExtended,
xtCExtended: begin AddAssignment('0.0'); AddAssignment('1.0'); end;
xtCurrency: AddAssignment('0.00');
xtComp,
xtInt64,
xtCardinal,
xtQWord: AddAssignment('0');
xtBoolean,
xtByteBool,
xtWordBool,
xtLongBool,
xtQWordBool: begin AddAssignment('False'); AddAssignment('True'); end;
xtString,
xtAnsiString,
xtShortString,
xtWideString,
xtUnicodeString: AddAssignment('''''');
xtPChar: begin AddAssignment('nil'); AddAssignment('#0'); end;
xtPointer: AddAssignment('nil');
xtConstOrdInteger: AddAssignment('0');
xtConstString: AddAssignment('''''');
xtConstReal: AddAssignment('0.0');
xtConstSet: AddAssignment('[]');
xtConstBoolean: begin AddAssignment('False'); AddAssignment('True'); end;
xtLongint,
xtLongWord,
xtWord,
xtSmallInt,
xtShortInt,
xtByte,
xtNativeInt,
xtNativeUInt: AddAssignment('0');
xtVariant: begin AddAssignment('0'); AddAssignment(''''''); end;
xtJSValue: begin AddAssignment('0'); AddAssignment(''''''); AddAssignment('nil'); AddAssignment('false'); end;
end;
if Statements.Count=0 then begin
MoveCursorToAtomPos(IdentAtom);
RaiseException(20170421201711,'auto initialize not yet implemented for identifier "'+GetIdentifier(Identifier)+'" of type "'+ExprTypeToString(ExprType)+'"');
end;
// find possible insert positions
BeginNode:=CursorNode.GetNodeOfType(ctnBeginBlock);
if BeginNode<>nil then begin
InsertPosDesc:=TInsertStatementPosDescription.Create;
InsertPosDesc.InsertPos:=BeginNode.StartPos+length('begin');
CleanPosToCaret(InsertPosDesc.InsertPos,InsertPosDesc.CodeXYPos);
InsertPosDesc.Indent:=GetLineIndent(Src,BeginNode.StartPos);
if SourceChangeCache<>nil then
inc(InsertPosDesc.Indent,SourceChangeCache.BeautifyCodeOptions.Indent)
else
inc(InsertPosDesc.Indent,2);
InsertPosDesc.FrontGap:=gtNewLine;
InsertPosDesc.AfterGap:=gtNewLine;
InsertPosDesc.Description:='After BEGIN keyword';
if (BeginNode.Parent<>nil) then begin
if BeginNode.Parent.Desc=ctnProcedure then
InsertPosDesc.Description+=' of '
+ExtractProcHead(BeginNode.Parent,[phpWithStart,phpAddClassName,phpWithoutParamList]);
end;
InsertPositions.Add(InsertPosDesc);
end;
if InsertPositions.Count=0 then begin
MoveCursorToAtomPos(IdentAtom);
RaiseException(20170421201714,'auto initialize not yet implemented for this context (Node='+CursorNode.DescAsString+')');
end;
end;
function TCodeCompletionCodeTool.GuessTypeOfIdentifier(
CursorPos: TCodeXYPosition; out IsKeyword, IsSubIdentifier: boolean;
out ExistingDefinition: TFindContext; out ListOfPFindContext: TFPList;
out NewExprType: TExpressionType; out NewType: string): boolean;
{ examples:
identifier:=<something>
aclass.identifier:=<something>
<something>:=aclass.identifier
<something>:=<something>+aclass.identifier
for identifier in <something>
ToDo: <proc>(,,aclass.identifier)
checks where the identifier is already defined or is a keyword
checks if the identifier is a sub identifier (e.g. A.identifier)
creates the list of possible insert locations
checks if it is the target of an assignment and guesses the type
checks if it is the run variable of an for in and guesses the type
ToDo: checks if it is a parameter and guesses the type
}
var
CleanCursorPos: integer;
Params: TFindDeclarationParams;
CursorNode: TCodeTreeNode;
IdentifierAtom: TAtomPosition;
TermAtom: TAtomPosition;
i: Integer;
Context: PFindContext;
Section: TCodeTreeNode;
ExistingNodeInProc: Boolean;
Keep: Boolean;
InAtomEndPos: Integer;
begin
Result:=false;
IsKeyword:=false;
IsSubIdentifier:=false;
ExistingDefinition:=CleanFindContext;
ListOfPFindContext:=nil;
NewExprType:=CleanExpressionType;
NewType:='';
BuildTreeAndGetCleanPos(CursorPos, CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
// find identifier name
GetIdentStartEndAtPosition(Src,CleanCursorPos,
IdentifierAtom.StartPos,IdentifierAtom.EndPos);
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln('TCodeCompletionCodeTool.GuessTypeOfIdentifier A Atom=',GetAtom(IdentifierAtom),' "',dbgstr(Src,CleanCursorPos,10),'"');
{$ENDIF}
if IdentifierAtom.StartPos=IdentifierAtom.EndPos then exit;
Result:=true;
MoveCursorToAtomPos(IdentifierAtom);
if AtomIsKeyWord then begin
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier is keyword: ',GetAtom]);
{$ENDIF}
IsKeyword:=true;
exit;
end;
// search identifier
ActivateGlobalWriteLock;
try
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
{$IF defined(CTDEBUG) or defined(VerboseGuessTypeOfIdentifier)}
DebugLn(' GuessTypeOfIdentifier: check if variable is already defined ...');
{$ENDIF}
// check if identifier exists
Result:=IdentifierIsDefined(IdentifierAtom,CursorNode,Params);
if Result then begin
// identifier is already defined
ExistingDefinition.Tool:=Params.NewCodeTool;
ExistingDefinition.Node:=Params.NewNode;
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier identifier already defined at ',FindContextToString(ExistingDefinition)]);
{$ENDIF}
end;
finally
Params.Free;
end;
// find all possible contexts
if not FindIdentifierContextsAtStatement(IdentifierAtom.StartPos,
IsSubIdentifier,ListOfPFindContext)
then begin
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier FindIdentifierContextsAtStatement failed']);
{$ENDIF}
exit;
end;
// remove contexts conflicting with the already defined identifier
if (ExistingDefinition.Node<>nil) and (ListOfPFindContext<>nil) then begin
Section:=ExistingDefinition.Node;
while Section<>nil do begin
if Section.Desc in AllDefinitionSections then break;
Section:=Section.Parent;
end;
ExistingNodeInProc:=ExistingDefinition.Node.HasParentOfType(ctnProcedure);
if Section<>nil then begin
for i:=ListOfPFindContext.Count-1 downto 0 do begin
Context:=PFindContext(ListOfPFindContext[i]);
Keep:=true;
if ExistingNodeInProc then begin
if (Context^.Tool<>ExistingDefinition.Tool)
or (Context^.Node.StartPos<=ExistingDefinition.Node.StartPos) then
Keep:=false; // existing is local var => delete all outside
end;
if Keep
and (Context^.Tool=ExistingDefinition.Tool)
and (((ExistingDefinition.Node=Context^.Node)
or ExistingDefinition.Node.HasAsParent(Context^.Node)))
then begin
// context is outside or same as existing context
// (e.g. identifier is already defined in the class) => delete
Keep:=false;
end;
if Keep then continue;
Dispose(Context);
ListOfPFindContext.Delete(i);
end;
end;
end;
// find assignment operator :=
MoveCursorToAtomPos(IdentifierAtom);
ReadNextAtom;
if AtomIs(':=') then begin
// is assignment
//AssignmentOperator:=CurPos;
// find term
ReadNextAtom;
TermAtom.StartPos:=CurPos.StartPos;
TermAtom.EndPos:=FindEndOfExpression(TermAtom.StartPos);
if TermAtom.StartPos=TermAtom.EndPos then begin
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier nothing behind := operator']);
{$ENDIF}
exit;
end;
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier guessing type of assignment :="',dbgstr(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos),'"']);
{$ENDIF}
// find type of term
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
NewType:=FindTermTypeAsString(TermAtom,Params,NewExprType);
finally
Params.Free;
end;
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier Assignment type=',NewType]);
{$ENDIF}
Result:=true;
end;
if not Result then begin
MoveCursorToAtomPos(IdentifierAtom);
// find 'in' operator
ReadNextAtom;
if UpAtomIs('IN') then begin
InAtomEndPos:=CurPos.EndPos;
// find 'for' keyword
MoveCursorToCleanPos(IdentifierAtom.StartPos);
ReadPriorAtom;
if not UpAtomIs('FOR') then exit;
// find term
MoveCursorToCleanPos(InAtomEndPos);
ReadNextAtom;
TermAtom.StartPos:=CurPos.StartPos;
TermAtom.EndPos:=FindEndOfExpression(TermAtom.StartPos);
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier guessing type of for-in list "',dbgstr(Src,TermAtom.StartPos,TermAtom.EndPos-TermAtom.StartPos),'"']);
{$ENDIF}
// find type of term
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
NewType:=FindForInTypeAsString(TermAtom,CursorNode,Params,NewExprType);
finally
Params.Free;
end;
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier For-In type=',NewType]);
{$ENDIF}
Result:=true;
end;
end;
if not Result then begin
{$IFDEF VerboseGuessTypeOfIdentifier}
debugln(['TCodeCompletionCodeTool.GuessTypeOfIdentifier can not guess type']);
{$ENDIF}
exit;
end;
finally
DeactivateGlobalWriteLock;
end;
end;
function TCodeCompletionCodeTool.DeclareVariableNearBy(
InsertPos: TCodeXYPosition; const VariableName, NewType, NewUnitName: string;
Visibility: TCodeTreeNodeDesc; SourceChangeCache: TSourceChangeCache;
LevelPos: TCodeXYPosition): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
NewPos: TCodeXYPosition;
NewTopLine: integer;
Node: TCodeTreeNode;
ClassPart: TNewClassPart;
LevelCleanPos: integer;
begin
Result:=false;
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.DeclareVariableNearBy InsertPos=',dbgs(InsertPos),' Name="',VariableName,'" Type="',NewType,'" Unit=',NewUnitName,' LevelPos=',dbgs(LevelPos)]);
{$ENDIF}
BuildTreeAndGetCleanPos(InsertPos,CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
CaretToCleanPos(LevelPos,LevelCleanPos);
if LevelCleanPos>0 then begin
Node:=FindDeepestNodeAtPos(LevelCleanPos,false);
while Node<>nil do begin
//debugln(['TCodeCompletionCodeTool.DeclareVariableNearBy Node=',Node.DescAsString]);
if Node.Desc in AllClassObjects then begin
// class member
debugln(['TCodeCompletionCodeTool.DeclareVariableNearBy class member']);
// initialize class for code completion
InitClassCompletion(Node,SourceChangeCache);
// check if variable already exists
if VarExistsInCodeCompleteClass(UpperCaseStr(VariableName)) then begin
debugln(['TCodeCompletionCodeTool.DeclareVariableNearBy member already exists: ',VariableName,' Class=',ExtractClassName(Node,false)]);
exit;
end;
ClassPart:=ncpPublishedVars;
case Visibility of
ctnClassPrivate: ClassPart:=ncpPrivateVars;
ctnClassProtected: ClassPart:=ncpProtectedVars;
ctnClassPublic: ClassPart:=ncpPublicVars;
end;
AddClassInsertion(UpperCaseStr(VariableName),
VariableName+':'+NewType+';',VariableName,ClassPart);
if not InsertAllNewClassParts then
RaiseException(20170421201717,ctsErrorDuringInsertingNewClassParts);
if (NewUnitName<>'')
and (not IsHiddenUsedUnit(PChar(NewUnitName)))
and (not AddUnitToMainUsesSection(NewUnitName,'',SourceChangeCache)) then
begin
debugln(['TCodeCompletionCodeTool.DeclareVariableNearBy AddUnitToMainUsesSection for new class memeber failed']);
exit;
end;
// apply the changes
if not SourceChangeCache.Apply then
RaiseException(20170421201720,ctsUnableToApplyChanges);
exit(true);
end;
Node:=Node.Parent;
end;
end;
SourceChangeCache.MainScanner:=Scanner;
Node:=CursorNode;
Result:=AddLocalVariable(CleanCursorPos,1,VariableName,NewType,NewUnitName,
NewPos,NewTopLine,SourceChangeCache,LevelCleanPos);
end;
function TCodeCompletionCodeTool.DeclareVariableAt(CursorPos: TCodeXYPosition;
const VariableName, NewType, NewUnitName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
NewCode: String;
FrontGap: TGapTyp;
AfterGap: TGapTyp;
InsertPos: Integer;
Indent: Integer;
Node: TCodeTreeNode;
NeedSection: Boolean;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.DeclareVariableAt CursorPos=',dbgs(CursorPos),' Name="',VariableName,'" Type="',NewType,'" Unit=',NewUnitName]);
{$ENDIF}
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
SourceChangeCache.MainScanner:=Scanner;
InsertPos:=CleanCursorPos;
Indent:=0;
FrontGap:=gtNewLine;
AfterGap:=gtNewLine;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
{$IFDEF CTDEBUG}
debugln(['TCodeCompletionCodeTool.DeclareVariableAt CursorNode=',CursorNode.DescAsString]);
{$ENDIF}
NewCode:=VariableName+':'+NewType+';';
NeedSection:=false;
if CursorNode.Desc=ctnVarDefinition then begin
// insert in front of another var
CursorNode:=GetFirstGroupVarNode(CursorNode);
InsertPos:=CursorNode.StartPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
end else if CursorNode.Desc in (AllClassBaseSections
+[ctnVarSection,ctnRecordType,ctnClassClassVar])
then begin
// insert into a var section
if (CursorNode.FirstChild=nil)
or (CursorNode.FirstChild.StartPos>InsertPos) then begin
MoveCursorToNodeStart(CursorNode);
ReadNextAtom;
if (CurPos.EndPos<CursorNode.EndPos)
and ((CursorNode.FirstChild=nil) or (CursorNode.FirstChild.StartPos>CurPos.EndPos))
and (InsertPos<CurPos.EndPos) then
InsertPos:=CurPos.EndPos;
end;
if CursorNode.FirstChild<>nil then
Indent:=Beauty.GetLineIndent(Src,CursorNode.FirstChild.StartPos)
else
Indent:=Beauty.GetLineIndent(Src,CursorNode.StartPos)+Beauty.Indent;
end else if CursorNode.Desc in [ctnProcedure,ctnInterface,ctnImplementation,
ctnProgram,ctnLibrary,ctnPackage]
then begin
Node:=CursorNode.FirstChild;
if (Node<>nil) and (Node.Desc=ctnSrcName) then
Node:=Node.NextBrother;
// make sure to insert behind uses section and proc header
if (Node<>nil) and (Node.Desc in [ctnUsesSection,ctnProcedureHead]) then
begin
if (Node<>nil) and (InsertPos<Node.EndPos) then
InsertPos:=Node.EndPos;
end;
// find node in front
while (Node<>nil) and (Node.NextBrother<>nil)
and (Node.NextBrother.StartPos<InsertPos) do
Node:=Node.NextBrother;
if (Node<>nil) and (Node.Desc=ctnVarSection) then begin
// append to a var section
if Node.LastChild<>nil then
Indent:=Beauty.GetLineIndent(Src,Node.LastChild.StartPos)
else
Indent:=Beauty.GetLineIndent(Src,Node.StartPos)+Beauty.Indent;
end else begin
// start a new var section
NeedSection:=true;
if Node<>nil then
Indent:=Beauty.GetLineIndent(Src,Node.StartPos)
else if CursorNode.FirstChild<>nil then
Indent:=Beauty.GetLineIndent(Src,CursorNode.FirstChild.StartPos)
else
Indent:=Beauty.GetLineIndent(Src,CursorNode.StartPos);
end;
end else begin
// default: add the variable at cursor
NeedSection:=true;
end;
if NeedSection then
NewCode:='var'+Beauty.LineEnd+Beauty.GetIndentStr(Beauty.Indent)+NewCode;
NewCode:=Beauty.BeautifyStatement(NewCode,Indent,[bcfIndentExistingLineBreaks]);
SourceChangeCache.BeginUpdate;
try
if (NewUnitName<>'') then begin
if not AddUnitToMainUsesSection(NewUnitName,'',SourceChangeCache) then begin
debugln(['TCodeCompletionCodeTool.DeclareVariableAt AddUnitToMainUsesSection failed']);
exit;
end;
end;
{$IFDEF VerboseCompletionAdds}
debugln(['TCodeCompletionCodeTool.DeclareVariableAt NewCode="',dbgstr(NewCode),'"']);
{$ENDIF}
if not SourceChangeCache.Replace(FrontGap,AfterGap,InsertPos,InsertPos,NewCode)
then exit;
Result:=true;
finally
if not Result then
SourceChangeCache.Clear;
if not SourceChangeCache.EndUpdate then
Result:=false;
end;
end;
function TCodeCompletionCodeTool.InitClassCompletion(
const AClassName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
ClassNode: TCodeTreeNode;
begin
Result:=false;
BuildTree(lsrInitializationStart);
if ScannedRange<>lsrEnd then exit;
if (SourceChangeCache=nil) or (Scanner=nil) then exit;
ClassNode:=FindClassNodeInUnit(AClassName,true,false,false,true);
Result:=InitClassCompletion(ClassNode,SourceChangeCache);
end;
function TCodeCompletionCodeTool.InitClassCompletion(ClassNode: TCodeTreeNode;
SourceChangeCache: TSourceChangeCache): boolean;
begin
if (ClassNode=nil) then exit(false);
CodeCompleteClassNode:=ClassNode;
CodeCompleteSrcChgCache:=SourceChangeCache;
FreeClassInsertionList;
Result:=true;
end;
function TCodeCompletionCodeTool.ApplyClassCompletion(
AddMissingProcBodies: boolean): boolean;
begin
Result:=false;
try
// insert all new class parts
if not InsertAllNewClassParts then
RaiseException(20170421201722,ctsErrorDuringInsertingNewClassParts);
// insert all missing proc bodies
if AddMissingProcBodies and (not CreateMissingClassProcBodies(true)) then
RaiseException(20170421201724,ctsErrorDuringCreationOfNewProcBodies);
// apply the changes
if not CodeCompleteSrcChgCache.Apply then
RaiseException(20170421201726,ctsUnableToApplyChanges);
Result:=true;
finally
FreeClassInsertionList;
end;
end;
function TCodeCompletionCodeTool.CompleteProperty(
PropNode: TCodeTreeNode): boolean;
{
examples:
property Visible;
property Count: integer;
property Color: TColor read FColor write SetColor;
property Items[Index1, Index2: integer]: integer read GetItems; default;
property X: integer index 1 read GetCoords write SetCoords stored IsStored;
property C: char read GetC stored False default 'A';
property Col8: ICol8 read FCol8 write FCol8 implements ICol8, IColor;
property Visible: WordBool readonly dispid 401;
property specifiers without parameters:
;nodefault, ;default
property specifiers with parameters:
index <id or number>, read <id>, write <id>, stored <id>,
default <constant>, implements <id>[,<id>...]
}
type
TPropPart = (ppName, // property name
ppParamList, // param list
ppType, // type identifier
ppIndexWord, // 'index'
ppIndex, // index constant
ppReadWord, // 'read'
ppRead, // read identifier
ppWriteWord, // 'write'
ppWrite, // write identifier
ppStoredWord, // 'stored'
ppStored, // stored identifier
ppImplementsWord,// 'implements'
ppImplements, // implements identifier
ppDefaultWord,// 'default' (the default value keyword,
// not the default property)
ppDefault, // default constant
ppNoDefaultWord,// 'nodefault'
ppDispidWord, // 'dispid'
ppDispid // dispid constant
);
var
Parts: array[TPropPart] of TAtomPosition;
PartIsAtom: array[TPropPart] of boolean; // is single identifier
procedure ReadSimpleSpec(SpecWord, SpecParam: TPropPart);
// allowed after simple specifier like 'read':
// one semicolon
// or an <identifier>
// or an <identifier>.<identifier>
// (only read, write: ) or an <identifier>[ordinal expression]
// or a specifier
begin
if Parts[SpecWord].StartPos>=1 then
RaiseExceptionFmt(20170421201731,ctsPropertySpecifierAlreadyDefined,[GetAtom]);
Parts[SpecWord]:=CurPos;
ReadNextAtom;
if AtomIsChar(';') then exit;
AtomIsIdentifierE;
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
then
exit;
Parts[SpecParam]:=CurPos;
ReadNextAtom;
while CurPos.Flag=cafPoint do begin
ReadNextAtom;
AtomIsIdentifierE;
ReadNextAtom;
PartIsAtom[SpecParam]:=false;
Parts[SpecParam].EndPos:=CurPos.EndPos;
end;
if (SpecParam in [ppRead,ppWrite])
and (CurPos.Flag=cafEdgedBracketOpen) then begin
// array access
PartIsAtom[SpecParam]:=false;
ReadTilBracketClose(true);
ReadNextAtom;
end;
end;
var
CleanAccessFunc, CleanParamList, ParamList, PropName, PropType, VariableName: string;
IsClassProp: boolean;
InsertPos: integer;
BeautifyCodeOpts: TBeautifyCodeOptions;
IndexType: string;
procedure InitCompleteProperty;
var APart: TPropPart;
begin
for APart:=Low(TPropPart) to High(TPropPart) do begin
Parts[APart].StartPos:=-1;
PartIsAtom[APart]:=true;
end;
IndexType:='Integer';
end;
procedure ReadPropertyKeywordAndName;
begin
MoveCursorToNodeStart(PropNode);
ReadNextAtom; // read 'property'
IsClassProp:=false;
if UpAtomIs('CLASS') then begin
IsClassProp:=true;
ReadNextAtom;
end;
ReadNextAtom; // read name
Parts[ppName]:=CurPos;
PropName := copy(Src,Parts[ppName].StartPos,
Parts[ppName].EndPos-Parts[ppName].StartPos);
if (PropName <> '') and (PropName[1] = '&') then//property name starts with '&'
Delete(PropName, 1, 1);
ReadNextAtom;
end;
procedure ReadPropertyParamList;
begin
if AtomIsChar('[') then begin
// read parameter list '[ ... ]'
Parts[ppParamList].StartPos:=CurPos.StartPos;
InitExtraction;
if not ReadParamList(true,true,[phpInUpperCase,phpWithoutBrackets])
then begin
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] error parsing param list');
{$ENDIF}
RaiseException(20170421201733,ctsErrorInParamList);
end;
CleanParamList:=GetExtraction(true);
Parts[ppParamList].EndPos:=CurPos.EndPos;
end else
CleanParamList:='';
end;
function ReadPropertyType: string;
procedure CheckIdentifier;
begin
if (CurPos.StartPos>PropNode.EndPos)
or UpAtomIs('END') or AtomIsChar(';') or (not AtomIsIdentifier)
or AtomIsKeyWord then begin
// no type name found -> ignore this property
RaiseExceptionFmt(20170421201735,ctsPropertTypeExpectedButAtomFound,[GetAtom]);
end;
end;
var
p: Integer;
begin
ReadNextAtom; // read type
CheckIdentifier;
Parts[ppType]:=CurPos;
Result:=GetAtom;
ReadTypeReference(false);
p:=LastAtoms.GetPriorAtom.EndPos;
if p>Parts[ppType].EndPos then begin
Parts[ppType].EndPos:=p;
Result:=ExtractCode(Parts[ppType].StartPos,Parts[ppType].EndPos,[]);
end;
end;
procedure ReadIndexSpecifier;
var
Last: TAtomPosition;
begin
if UpAtomIs('INDEX') then begin
if Parts[ppIndexWord].StartPos>=1 then
RaiseException(20170421201737,ctsIndexSpecifierRedefined);
Parts[ppIndexWord]:=CurPos;
ReadNextAtom;
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
RaiseExceptionFmt(20170421201740,ctsIndexParameterExpectedButAtomFound,[GetAtom]);
Parts[ppIndex].StartPos:=CurPos.StartPos;
ReadConstant(true,false,[]);
Last:=LastAtoms.GetPriorAtom;
Parts[ppIndex].EndPos:=Last.EndPos;
PartIsAtom[ppIndex]:=false;
end;
end;
procedure ReadDispidSpecifier;
begin
if UpAtomIs('DISPID') then begin
if Parts[ppDispidWord].StartPos>=1 then
RaiseException(20170421201742,ctsDispidSpecifierRedefined);
Parts[ppDispidWord]:=CurPos;
ReadNextAtom;
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
RaiseExceptionFmt(20170421201744,ctsDispidParameterExpectedButAtomFound,[GetAtom]);
Parts[ppDispid].StartPos:=CurPos.StartPos;
ReadConstant(true,false,[]);
Parts[ppDispid].EndPos:=LastAtoms.GetPriorAtom.EndPos;
PartIsAtom[ppDispid]:=false;
end;
end;
procedure ReadReadSpecifier;
begin
if UpAtomIs('READ') then ReadSimpleSpec(ppReadWord,ppRead);
end;
procedure ReadWriteSpecifier;
begin
if UpAtomIs('WRITE') then ReadSimpleSpec(ppWriteWord,ppWrite);
end;
procedure ReadOptionalSpecifiers;
begin
while (CurPos.StartPos<PropNode.EndPos) do begin
if (CurPos.Flag in [cafSemicolon,cafEnd]) then break;
if UpAtomIs('STORED') then begin
ReadSimpleSpec(ppStoredWord,ppStored);
end else if UpAtomIs('DEFAULT') then begin
if Parts[ppDefaultWord].StartPos>=1 then
RaiseException(20170421201746,ctsDefaultSpecifierRedefined);
Parts[ppDefaultWord]:=CurPos;
ReadNextAtom;
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
RaiseExceptionFmt(20170421201748,ctsDefaultParameterExpectedButAtomFound,[GetAtom]);
Parts[ppDefault].StartPos:=CurPos.StartPos;
ReadConstant(true,false,[]);
Parts[ppDefault].EndPos:=LastAtoms.GetPriorAtom.EndPos;
PartIsAtom[ppDefault]:=false;
end else if UpAtomIs('NODEFAULT') then begin
if Parts[ppNoDefaultWord].StartPos>=1 then
RaiseException(20170421201750,ctsNodefaultSpecifierDefinedTwice);
Parts[ppNoDefaultWord]:=CurPos;
ReadNextAtom;
end else if UpAtomIs('IMPLEMENTS') then begin
ReadSimpleSpec(ppImplementsWord,ppImplements);
while CurPos.Flag=cafComma do begin
ReadNextAtom;
AtomIsIdentifierE;
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
RaiseExceptionFmt(20170421201752,ctsIndexParameterExpectedButAtomFound,[GetAtom]);
ReadNextAtom;
end;
end else
RaiseExceptionFmt(20170421201755,ctsStrExpectedButAtomFound,[';',GetAtom]);
end;
end;
procedure ResolveIndexType;
var
ExprType: TExpressionType;
Params: TFindDeclarationParams;
begin
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions;
Params.ContextNode:=PropNode;
IndexType:=FindTermTypeAsString(Parts[ppIndex],Params,ExprType);
finally
Params.Free;
end;
end;
procedure CompleteReadSpecifier;
var
IsGetterFunc: boolean;
VarCode: String;
AccessParamPrefix: String;
AccessParam: String;
AccessFunc: String;
begin
// check read specifier
VariableName:='';
if not PartIsAtom[ppRead] then exit;
if (Parts[ppReadWord].StartPos<=0) and (Parts[ppWriteWord].StartPos>0) then
exit;
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] read specifier needed');
{$ENDIF}
AccessParamPrefix:=BeautifyCodeOpts.PropertyReadIdentPrefix;
if Parts[ppRead].StartPos>0 then
AccessParam:=copy(Src,Parts[ppRead].StartPos,
Parts[ppRead].EndPos-Parts[ppRead].StartPos)
else begin
if (Parts[ppParamList].StartPos>0) or (Parts[ppIndexWord].StartPos>0)
or (SysUtils.CompareText(AccessParamPrefix,
LeftStr(AccessParam,length(AccessParamPrefix)))=0)
or (CodeCompleteClassNode.Desc in AllClassInterfaces) then
begin
// create the default read identifier for a function
AccessParam:=AccessParamPrefix+PropName;
end else begin
// create the default read identifier for a variable
AccessParam:=BeautifyCodeOpts.PrivateVariablePrefix+PropName;
end;
end;
// complete read identifier in property definition
if (Parts[ppRead].StartPos<0) and CompleteProperties then begin
// insert read specifier
if Parts[ppReadWord].StartPos>0 then begin
// 'read' keyword exists -> insert read identifier behind
InsertPos:=Parts[ppReadWord].EndPos;
FSourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,AccessParam);
end else begin
// 'read' keyword does not exist -> insert behind index and type
if Parts[ppIndex].StartPos>0 then
InsertPos:=Parts[ppIndex].EndPos
else if Parts[ppIndexWord].StartPos>0 then
InsertPos:=Parts[ppIndexWord].EndPos
else
InsertPos:=Parts[ppType].EndPos;
FSourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
BeautifyCodeOpts.BeautifyKeyWord('read')+' '+AccessParam);
end;
end;
IsGetterFunc:=(Parts[ppParamList].StartPos>0)
or ((Parts[ppIndexWord].StartPos>0)
and not VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)))
or (SysUtils.CompareText(AccessParamPrefix,
LeftStr(AccessParam,length(AccessParamPrefix)))=0)
or (CodeCompleteClassNode.Desc in AllClassInterfaces);
if not IsGetterFunc then
VariableName:=AccessParam;
// check if read access method exists
if (Parts[ppIndexWord].StartPos<1) then begin
if (Parts[ppParamList].StartPos>0) then begin
// param list, no index
CleanAccessFunc:=UpperCaseStr(AccessParam)+'('+CleanParamList+');';
end else begin
// no param list, no index
CleanAccessFunc:=UpperCaseStr(AccessParam)+';';
end;
end else begin
// ToDo: find out type of index
if (Parts[ppParamList].StartPos>0) then begin
// param list + index
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+CleanParamList+';'+IndexType+');');
end else begin
// index, no param list
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+IndexType+');');
end;
end;
if ProcExistsInCodeCompleteClass(CleanAccessFunc) then exit;
// check if read access variable exists
if (Parts[ppParamList].StartPos<1)
and (CodeCompleteClassNode.Desc in AllClassObjects)
and VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)) then exit;
// complete read access specifier
if IsGetterFunc then begin
// the read identifier is a function
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] CleanAccessFunc ',CleanAccessFunc,' does not exist');
{$ENDIF}
// add insert demand for function
// build function code
if (Parts[ppParamList].StartPos>0) then begin
MoveCursorToCleanPos(Parts[ppParamList].StartPos);
ReadNextAtom;
InitExtraction;
if not ReadParamList(true,true,[phpWithParameterNames,
phpWithoutBrackets,phpWithVarModifiers,
phpWithComments])
then begin
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] Error reading param list');
{$ENDIF}
RaiseException(20170421201756,ctsErrorInParamList);
end;
ParamList:=GetExtraction(false);
if (Parts[ppIndexWord].StartPos<1) then begin
// param list, no index
AccessFunc:='function '+AccessParam
+'('+ParamList+'):'+PropType+';';
end else begin
// param list + index
AccessFunc:='function '+AccessParam
+'('+ParamList+'; AIndex:'+IndexType+'):'+PropType+';';
end;
end else begin
if (Parts[ppIndexWord].StartPos<1) then begin
// no param list, no index
AccessFunc:='function '+AccessParam+':'+PropType+';';
end else begin
// index, no param list
AccessFunc:='function '+AccessParam
+'(AIndex:'+IndexType+'):'+PropType+';';
end;
end;
if IsClassProp then
AccessFunc:='class '+AccessFunc+' static;';
// add new Insert Node
if CompleteProperties then
AddClassInsertion(CleanAccessFunc,AccessFunc,AccessParam,
ncpPrivateProcs,PropNode);
end else begin
// the read identifier is a variable
// variable does not exist yet -> add insert demand for variable
VarCode:=VariableName+':'+PropType+';';
if IsClassProp then VarCode:='class var '+VarCode;
AddClassInsertion(UpperCaseStr(VariableName),
VarCode,VariableName,ncpPrivateVars,PropNode);
end;
end;
procedure CompleteWriteSpecifier;
var
ProcBody: String;
AccessParamPrefix: String;
AccessParam: String;
AccessFunc: String;
AccessVariableName, AccessVariableNameParam: String;
begin
// check write specifier
if not PartIsAtom[ppWrite] then exit;
if (Parts[ppWriteWord].StartPos<1) and (Parts[ppReadWord].StartPos>0) then
exit;
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] write specifier needed');
{$ENDIF}
AccessParamPrefix:=BeautifyCodeOpts.PropertyWriteIdentPrefix;
if Parts[ppWrite].StartPos>0 then
AccessParam:=copy(Src,Parts[ppWrite].StartPos,
Parts[ppWrite].EndPos-Parts[ppWrite].StartPos)
else
AccessParam:=AccessParamPrefix+PropName;
// complete property definition for write specifier
if (Parts[ppWrite].StartPos<0) and CompleteProperties then begin
// insert write specifier
if Parts[ppWriteWord].StartPos>0 then begin
// 'write' keyword exists -> insert write identifier behind
InsertPos:=Parts[ppWriteWord].EndPos;
FSourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
AccessParam);
end else begin
// 'write' keyword does not exist
// -> insert behind type, index and write specifier
if Parts[ppRead].StartPos>0 then
InsertPos:=Parts[ppRead].EndPos
else if Parts[ppReadWord].StartPos>0 then
InsertPos:=Parts[ppReadWord].EndPos
else if Parts[ppIndex].StartPos>0 then
InsertPos:=Parts[ppIndex].EndPos
else if Parts[ppIndexWord].StartPos>0 then
InsertPos:=Parts[ppIndexWord].EndPos
else
InsertPos:=Parts[ppType].EndPos;
FSourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
BeautifyCodeOpts.BeautifyKeyWord('write')+' '+AccessParam);
end;
end;
// check if write method exists
if (Parts[ppIndexWord].StartPos<1) then begin
if (Parts[ppParamList].StartPos>0) then begin
// param list, no index
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+CleanParamList+';'
+PropType+');');
end else begin
// no param list, no index
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+PropType+');');
end;
end else begin
// ToDo: find out index type
if (Parts[ppParamList].StartPos>0) then begin
// param list + index
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+CleanParamList+';'+IndexType+';'+PropType+');');
end else begin
// index, no param list
CleanAccessFunc:=UpperCaseStr(AccessParam+'('+IndexType+';'+PropType+');');
end;
end;
if ProcExistsInCodeCompleteClass(CleanAccessFunc) then exit;
// check if write variable exists
if (Parts[ppParamList].StartPos<1)
and (CodeCompleteClassNode.Desc in AllClassObjects)
and VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)) then exit;
// complete class
if (Parts[ppParamList].StartPos>0)
or ((Parts[ppIndexWord].StartPos>0)
and not VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)))
or (SysUtils.CompareText(AccessParamPrefix,
LeftStr(AccessParam,length(AccessParamPrefix)))=0)
or (CodeCompleteClassNode.Desc in AllClassInterfaces) then
begin
// add insert demand for function
// build function code
ProcBody:='';
AccessVariableName := SetPropertyVariablename;
if SetPropertyVariableIsPrefix then
AccessVariableName := AccessVariableName+PropName;
if SetPropertyVariableUseConst then
AccessVariableNameParam := 'const '+AccessVariableName
else
AccessVariableNameParam := AccessVariableName;
if (Parts[ppParamList].StartPos>0) then begin
MoveCursorToCleanPos(Parts[ppParamList].StartPos);
ReadNextAtom;
InitExtraction;
if not ReadParamList(true,true,[phpWithParameterNames,
phpWithoutBrackets,phpWithVarModifiers,
phpWithComments])
then
RaiseException(20170421201758,ctsErrorInParamList);
ParamList:=GetExtraction(false);
if (Parts[ppIndexWord].StartPos<1) then begin
// param list, no index
AccessFunc:='procedure '+AccessParam
+'('+ParamList+';'+AccessVariableNameParam+':'
+PropType+');';
end else begin
// param list+ index
AccessFunc:='procedure '+AccessParam
+'('+ParamList+';AIndex:'+IndexType+';'
+AccessVariableNameParam+':'+PropType+');';
end;
end else begin
if (Parts[ppIndexWord].StartPos<1) then begin
// no param list, no index
AccessFunc:=
'procedure '+AccessParam
+'('+AccessVariableNameParam+':'+PropType+');';
if VariableName<>'' then begin
{ read spec is a variable -> add simple assign code to body
For example:
procedure SetMyInt(AValue: integer);
begin
if FMyInt=AValue then exit;
FMyInt:=AValue;
end;
}
{$IFDEF EnableCodeCompleteTemplates}
if assigned(CTTemplateExpander)
and CTTemplateExpander.TemplateExists('SetterMethod') then
begin
debugln(['CompleteWriteSpecifier ', 'USING template for SetterMethod']);
ProcBody := CTTemplateExpander.Expand( 'SetterMethod',
BeautifyCodeOpts.LineEnd,
GetIndentStr(BeautifyCodeOpts.Indent),
['ClassName', 'AccessParam','PropVarName', 'PropType','VarName'],
[ExtractClassName(PropNode.Parent.Parent,false), AccessParam, SetPropertyVariablename, PropType, VariableName] );
end
else
{$ENDIF}
begin
ProcBody:=
'procedure '
+ExtractClassName(PropNode.Parent.Parent,false,true,Scanner.CompilerMode in [cmDELPHI,cmDELPHIUNICODE])+'.'+AccessParam
+'('+AccessVariableNameParam+':'+PropType+');'
+BeautifyCodeOpts.LineEnd
+'begin'+BeautifyCodeOpts.LineEnd
+BeautifyCodeOpts.GetIndentStr(BeautifyCodeOpts.Indent)
+'if '+VariableName+'='+AccessVariableName+' then Exit;'
+BeautifyCodeOpts.LineEnd
+BeautifyCodeOpts.GetIndentStr(BeautifyCodeOpts.Indent)
+VariableName+':='+AccessVariableName+';'
+BeautifyCodeOpts.LineEnd
+'end;';
end;
if IsClassProp then
ProcBody:='class '+ProcBody;
end;
end else begin
// index, no param list
AccessFunc:='procedure '+AccessParam
+'(AIndex:'+IndexType+';'+AccessVariableNameParam+':'+PropType+');';
end;
end;
// add new Insert Node
if IsClassProp then
AccessFunc:='class '+AccessFunc+' static;';
if CompleteProperties then
AddClassInsertion(CleanAccessFunc,AccessFunc,AccessParam,
ncpPrivateProcs,PropNode,ProcBody);
end else begin
// the write identifier is a variable
// -> add insert demand for variable
if CompleteProperties then
AddClassInsertion(UpperCaseStr(AccessParam),
AccessParam+':'+PropType+';',AccessParam,ncpPrivateVars,PropNode);
end;
end;
procedure CompleteStoredSpecifier;
var
AccessParam: String;
AccessFunc: String;
begin
// check stored specifier
if not PartIsAtom[ppStored] then exit;
if (Parts[ppStoredWord].StartPos<1) then exit;
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] stored specifier needed');
{$ENDIF}
if Parts[ppStored].StartPos>0 then begin
if (CompareIdentifiers(@Src[Parts[ppStored].StartPos],'False')=0)
or (CompareIdentifiers(@Src[Parts[ppStored].StartPos],'True')=0) then
exit;
AccessParam:=copy(Src,Parts[ppStored].StartPos,
Parts[ppStored].EndPos-Parts[ppStored].StartPos);
end else
AccessParam:=PropName
+BeautifyCodeOpts.PropertyStoredIdentPostfix;
if (Parts[ppIndexWord].StartPos<1) then begin
// no index -> check if method or field exists
CleanAccessFunc:=UpperCaseStr(AccessParam);
if (not ProcExistsInCodeCompleteClass(CleanAccessFunc+';'))
and (not VarExistsInCodeCompleteClass(CleanAccessFunc))
then begin
// add insert demand for function
// build function code
AccessFunc := 'function ' + AccessParam + ':Boolean;';
CleanAccessFunc := CleanAccessFunc+';';
if IsClassProp then
AccessFunc:='class '+AccessFunc+' static;';;
// add new Insert Node
if CompleteProperties then
AddClassInsertion(CleanAccessFunc,AccessFunc,AccessParam,
ncpPrivateProcs,PropNode);
end;
end else begin
// has index specifier -> check if method exists
CleanAccessFunc:=UpperCaseStr(AccessParam);
if (not ProcExistsInCodeCompleteClass(CleanAccessFunc+'('+UpperCaseStr(IndexType)+');'))
and (not VarExistsInCodeCompleteClass(CleanAccessFunc))
then begin
// add insert demand for function
// build function code
AccessFunc := 'function ' + AccessParam + '(AIndex:'+IndexType+'):Boolean;';
CleanAccessFunc := UpperCaseStr(CleanAccessFunc + '('+IndexType+');');
if IsClassProp then
AccessFunc:='class '+AccessFunc+' static;';
// add new Insert Node
if CompleteProperties then
AddClassInsertion(CleanAccessFunc,AccessFunc,AccessParam,
ncpPrivateProcs,PropNode);
end;
end;
if Parts[ppStored].StartPos<0 then begin
// insert stored specifier
InsertPos:=Parts[ppStoredWord].EndPos;
if CompleteProperties then
FSourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
AccessParam);
end;
end;
procedure CompleteSemicolon;
begin
if (PropNode.EndPos<=SrcLen) and (Src[PropNode.EndPos-1]<>';') then begin
InsertPos:=PropNode.EndPos;
if CompleteProperties then
FSourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertPos,';');
end;
end;
begin
Result:=false;
InitCompleteProperty;
ReadPropertyKeywordAndName;
ReadPropertyParamList;
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] Checking Property ',GetAtom);
{$ENDIF}
if not AtomIsChar(':') then begin
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CompleteProperty] no type : found -> ignore property');
{$ENDIF}
// no type -> ignore this property
Result:=true;
exit;
end;
PropType:=ReadPropertyType;
// parse specifiers
if CodeCompleteClassNode.Desc <> ctnDispinterface then begin
ReadIndexSpecifier;
ReadReadSpecifier;
ReadWriteSpecifier;
ReadOptionalSpecifiers;
end else begin
if UpAtomIs('READONLY') or UpAtomIs('WRITEONLY') then
ReadNextAtom;
ReadDispidSpecifier;
end;
// complete property
BeautifyCodeOpts:=FSourceChangeCache.BeautifyCodeOptions;
if CodeCompleteClassNode.Desc <> ctnDispinterface then begin
if Parts[ppIndex].StartPos>0 then
ResolveIndexType;
CompleteReadSpecifier;
CompleteWriteSpecifier;
CompleteStoredSpecifier;
end;
CompleteSemicolon;
Result:=true;
end;
function TCodeCompletionCodeTool.GetFirstClassIdentifier(
ClassNode: TCodeTreeNode): TCodeTreeNode;
const
Identifiers = AllIdentifierDefinitions+[ctnProperty,ctnProcedure,ctnClassGUID];
begin
if ClassNode=nil then exit(nil);
Result:=ClassNode.FirstChild;
while Result<>nil do begin
if (Result.Desc in Identifiers) then
exit;
Result:=FindNextIdentNodeInClass(Result);
end;
end;
procedure TCodeCompletionCodeTool.InsertNewClassParts(PartType: TNewClassPart);
var ANodeExt: TCodeTreeNodeExtension;
ClassSectionNode, ANode, InsertNode: TCodeTreeNode;
Indent, InsertPos: integer;
CurCode: string;
IsVariable, InsertBehind: boolean;
Visibility: TPascalClassSection;
Beauty: TBeautifyCodeOptions;
begin
ANodeExt:=FirstInsert;
Visibility:=NewClassPartVisibility[PartType];
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
// insert all nodes of specific type
while ANodeExt<>nil do begin
IsVariable:=NodeExtIsVariable(ANodeExt);
if (cardinal(ord(PartType))=ANodeExt.Flags) then begin
// search a destination section
ClassSectionNode:=nil;
if Visibility=pcsPublished then begin
// insert into first published section
ClassSectionNode:=CodeCompleteClassNode.FirstChild;
while not (ClassSectionNode.Desc in AllClassSections) do
ClassSectionNode:=ClassSectionNode.NextBrother;
// the first class section is always a published section, even if there
// is no 'published' keyword. If the class starts with the 'published'
// keyword, then it will be more beautiful to insert vars and procs to
// this second published section
if (ClassSectionNode.FirstChild=nil)
and (ClassSectionNode.NextBrother<>nil)
and (ClassSectionNode.NextBrother.Desc=ctnClassPublished)
then
ClassSectionNode:=ClassSectionNode.NextBrother;
end else if ANodeExt.Node<>nil then begin
// search a section of the same Visibility in front of the node
if CodeCompleteClassNode.Desc in AllClassObjects then
begin
ClassSectionNode:=ANodeExt.Node.Parent.PriorBrother;
while (ClassSectionNode<>nil)
and (ClassSectionNode.Desc<>ClassSectionNodeType[Visibility]) do
ClassSectionNode:=ClassSectionNode.PriorBrother;
end else begin
ClassSectionNode:=CodeCompleteClassNode;
end;
end else begin
// search a section of the same Visibility
if CodeCompleteClassNode.Desc in AllClassObjects then
begin
ClassSectionNode:=CodeCompleteClassNode.FirstChild;
while (ClassSectionNode<>nil)
and (ClassSectionNode.Desc<>ClassSectionNodeType[Visibility]) do
ClassSectionNode:=ClassSectionNode.NextBrother;
end else begin
ClassSectionNode:=CodeCompleteClassNode;
end;
end;
if ClassSectionNode=nil then begin
// there is no existing class section node
// -> insert in the new one
Indent:=NewClassSectionIndent[Visibility]+Beauty.Indent;
InsertPos:=NewClassSectionInsertPos[Visibility];
if InsertPos<1 then
raise Exception.Create('TCodeCompletionCodeTool.InsertNewClassParts inconsistency: missing section: please create a bug report');
end else begin
// there is an existing class section to insert into
// find a nice insert position
InsertNode:=nil; // the new part will be inserted after this node
// nil means insert as first
InsertBehind:=true;
ANode:=ClassSectionNode.FirstChild;
// skip the class GUID
if (ANode<>nil) and (ANode.Desc=ctnClassGUID) then begin
InsertNode:=ANode;
ANode:=ANode.NextBrother;
end;
// insert methods behind variables
if not IsVariable then begin
while (ANode<>nil) and (ANode.Desc=ctnVarDefinition) do begin
InsertNode:=ANode;
ANode:=ANode.NextBrother;
end;
end;
// find a nice position between similar siblings
case Beauty.ClassPartInsertPolicy of
cpipAlphabetically:
begin
while ANode<>nil do begin
if IsVariable then begin
// the insertion is a new variable
if (ANode.Desc<>ctnVarDefinition)
or (CompareNodeIdentChars(ANode,ANodeExt.Txt)<0) then
break;
end else begin
// the insertion is a new method
case ANode.Desc of
ctnProcedure:
begin
CurCode:=ExtractProcName(ANode,[]);
if SysUtils.CompareText(CurCode,ANodeExt.ExtTxt2)>0 then
break;
end;
ctnProperty:
begin
if FSourceChangeCache.BeautifyCodeOptions
.MixMethodsAndProperties then
begin
CurCode:=ExtractPropName(ANode,false);
if SysUtils.CompareText(CurCode,ANodeExt.ExtTxt2)>0 then
break;
end else
break;
end;
end;
end;
InsertNode:=ANode;
ANode:=ANode.NextBrother;
end;
end;
else
// cpipLast
begin
while ANode<>nil do begin
if IsVariable then begin
// the insertion is a variable
if (ANode.Desc<>ctnVarDefinition) then
break;
end else begin
// the insertion is a method
if (not Beauty.MixMethodsAndProperties)
and (ANode.Desc=ctnProperty) then
break;
end;
InsertNode:=ANode;
ANode:=ANode.NextBrother;
end;
end
end;
if InsertNode<>nil then begin
//debugln(['TCodeCompletionCodeTool.InsertNewClassParts insert behind existing']);
// for variable lists: a,b,c: integer
// use last node
if InsertBehind then begin
while (InsertNode.Desc=ctnVarDefinition)
and (InsertNode.FirstChild=nil)
and (InsertNode.NextBrother<>nil)
and (InsertNode.NextBrother.Desc=ctnVarDefinition) do
InsertNode:=InsertNode.NextBrother;
end;
if (not IsVariable) and (InsertNode.Desc=ctnVarDefinition)
and (InsertNode.NextBrother<>nil) then begin
// insertion is a new method and it should be inserted behind
// variables. Because methods and variables should be separated
// there is a next node, insert the new method in front of the next
// node, instead of inserting it right behind the variable.
// This makes sure to use existing separation comments/empty lines.
InsertNode:=InsertNode.NextBrother;
InsertBehind:=false;
end;
Indent:=Beauty.GetLineIndent(Src,InsertNode.StartPos);
if InsertBehind then begin
// insert behind InsertNode
InsertPos:=FindLineEndOrCodeAfterPosition(InsertNode.EndPos);
end else begin
// insert in front of InsertNode
InsertPos:=InsertNode.StartPos;
end;
end else begin
// insert as first variable/proc
//debugln(['TCodeCompletionCodeTool.InsertNewClassParts insert as first var: ',ClassSectionNode.DescAsString,' ',dbgstr(copy(Src,ClassSectionNode.StartPos,ClassSectionNode.EndPos-ClassSectionNode.StartPos))]);
Indent:=Beauty.GetLineIndent(Src,ClassSectionNode.StartPos)+Beauty.Indent;
InsertPos:=ClassSectionNode.StartPos;
if (ClassSectionNode.Desc=ctnClassPublished)
and (CompareIdentifiers(@Src[ClassSectionNode.StartPos],'published')<>0)
then begin
// the first published section has no keyword
if ClassSectionNode.NextBrother<>nil then
Indent:=Beauty.GetLineIndent(Src,ClassSectionNode.NextBrother.StartPos)
+Beauty.Indent
else
Indent:=Beauty.GetLineIndent(Src,ClassSectionNode.Parent.StartPos)
+Beauty.Indent;
end else if (ClassSectionNode.Desc in AllClassBaseSections)
then begin
// skip keyword
MoveCursorToCleanPos(InsertPos);
ReadNextAtom;
if UpAtomIs('STRICT') then
ReadNextAtom;
//debugln(['TCodeCompletionCodeTool.InsertNewClassParts insert as first of ',ClassSectionNode.DescAsString,' Atom=',GetAtom]);
ANode:=ClassSectionNode.Next;
if (ANode<>nil) and (CurPos.EndPos<=ANode.StartPos) then
InsertPos:=CurPos.EndPos;
end else if ClassSectionNode.Desc in AllClassInterfaces then begin
// skip class interface header
MoveCursorToCleanPos(InsertPos);
ReadNextAtom; // skip 'interface'
InsertPos:=CurPos.EndPos;
if ReadNextAtomIsChar('(') then begin
ReadTilBracketClose(true);
InsertPos:=CurPos.EndPos;
end;
end;
//debugln(['TCodeCompletionCodeTool.InsertNewClassParts insert as first, somewhere after InsertPos=',CleanPosToStr(InsertPos)]);
InsertPos:=FindLineEndOrCodeAfterPosition(InsertPos);
//debugln(['TCodeCompletionCodeTool.InsertNewClassParts insert as first, InsertPos=',CleanPosToStr(InsertPos)]);
end;
end;
CurCode:=ANodeExt.ExtTxt1;
CurCode:=Beauty.BeautifyStatement(CurCode,Indent,[bcfChangeSymbolToBracketForGenericTypeBrackets]);
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.InsertNewClassParts:');
DebugLn(CurCode);
{$ENDIF}
FSourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
CurCode);
if (not IsVariable) and (Beauty.MethodInsertPolicy=mipClassOrder) then
begin
// this was a new method definition and the body should be added in
// Class Order
// -> save information about the inserted position
ANodeExt.Position:=InsertPos;
end;
end;
ANodeExt:=ANodeExt.Next;
end;
end;
function TCodeCompletionCodeTool.InsertAllNewClassParts: boolean;
var
NewSectionKeyWordNeeded: boolean;
NewSection: TPascalClassSection;
Beauty: TBeautifyCodeOptions;
function GetTopMostPositionNode(Visibility: TPascalClassSection
): TCodeTreeNode;
var
ANodeExt: TCodeTreeNodeExtension;
begin
Result:=nil;
ANodeExt:=FirstInsert;
while ANodeExt<>nil do begin
if (ANodeExt.Node<>nil)
and ((Result=nil) or (Result.StartPos>ANodeExt.Node.StartPos))
and (NodeExtHasVisibilty(ANodeExt,Visibility))
then
Result:=ANodeExt.Node;
ANodeExt:=ANodeExt.Next;
end;
end;
function GetFirstNodeExtWithVisibility(Visibility: TPascalClassSection
): TCodeTreeNodeExtension;
begin
Result:=FirstInsert;
while Result<>nil do begin
if NodeExtHasVisibilty(Result,Visibility) then
break;
Result:=Result.Next;
end;
end;
function GetFirstVisibilitySectionNode: TCodeTreeNode;
begin
if CodeCompleteClassNode.Desc in AllClassInterfaces then
Result:=CodeCompleteClassNode
else begin
Result:=CodeCompleteClassNode.FirstChild;
while not (Result.Desc in AllClassBaseSections) do
Result:=Result.NextBrother;
end;
end;
procedure AddClassSection(Visibility: TPascalClassSection);
var
TopMostPositionNode: TCodeTreeNode;
SectionNode: TCodeTreeNode;
SectionKeyWord: String;
ANode: TCodeTreeNode;
FirstVisibilitySection: TCodeTreeNode;
NewCode: String;
Beauty: TBeautifyCodeOptions;
begin
NewClassSectionInsertPos[Visibility]:=-1;
NewClassSectionIndent[Visibility]:=0;
if CodeCompleteClassNode.Desc in AllClassInterfaces then begin
// a class interface has no sections
exit;
end;
// check if section is needed
if GetFirstNodeExtWithVisibility(Visibility)=nil then exit;
// search topmost position node for this Visibility
TopMostPositionNode:=GetTopMostPositionNode(Visibility);
SectionNode:=nil;
// search a Visibility section in front of topmost position node
if TopMostPositionNode<>nil then begin
SectionNode:=TopMostPositionNode;
while (SectionNode<>nil) and (SectionNode.Parent<>CodeCompleteClassNode)
do
SectionNode:=SectionNode.Parent;
if SectionNode<>nil then
SectionNode:=SectionNode.PriorBrother;
end else
SectionNode:=CodeCompleteClassNode.LastChild;
while (SectionNode<>nil)
and (SectionNode.Desc<>ClassSectionNodeType[Visibility]) do
SectionNode:=SectionNode.PriorBrother;
if (SectionNode<>nil) then begin
//DebugLn(['AddClassSection section exists for ',NodeDescriptionAsString(ClassSectionNodeType[Visibility])]);
exit;
end;
{ There is no section of this Visibility in front (or at all)
-> Insert a new section in front of topmost node.
Normally the best place for a new section is at the end of
the first published section. But if a variable is already
needed in the first published section, then the new section
must be inserted in front of all }
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
FirstVisibilitySection:=GetFirstVisibilitySectionNode;
if (TopMostPositionNode<>nil)
and (FirstVisibilitySection<>nil)
and ((TopMostPositionNode.HasAsParent(FirstVisibilitySection)
or (TopMostPositionNode=FirstVisibilitySection)))
then begin
// topmost node is in the first section
// -> insert the new section as the first section
ANode:=FirstVisibilitySection;
NewClassSectionIndent[Visibility]:=Beauty.GetLineIndent(Src,ANode.StartPos);
if (ANode.FirstChild<>nil) and (ANode.FirstChild.Desc<>ctnClassGUID)
then
NewClassSectionInsertPos[Visibility]:=ANode.StartPos
else
NewClassSectionInsertPos[Visibility]:=ANode.FirstChild.EndPos;
if (not NewSectionKeyWordNeeded)
and (CompareNodeIdentChars(ANode, UpperCase(PascalClassSectionKeywords[NewSection]))<>0) then begin
NewSectionKeyWordNeeded:=true;
NewClassSectionInsertPos[NewSection]:=
NewClassSectionInsertPos[Visibility];
NewClassSectionIndent[NewSection]:=
NewClassSectionIndent[Visibility];
end;
end else begin
ANode:=nil;
case Visibility of
pcsProtected:
// insert after last private section
ANode:=FindLastClassSection(CodeCompleteClassNode,ctnClassPrivate);
pcsPublic:
begin
// insert after last private, protected section
ANode:=FindClassSection(CodeCompleteClassNode,ctnClassProtected);
if ANode=nil then
ANode:=FindClassSection(CodeCompleteClassNode,ctnClassPrivate);
end;
end;
if ANode=nil then begin
// default: insert new section behind first published section
ANode:=FirstVisibilitySection;
end;
NewClassSectionIndent[Visibility]:=Beauty.GetLineIndent(Src,ANode.StartPos);
NewClassSectionInsertPos[Visibility]:=ANode.EndPos;
end;
SectionKeyWord:=PascalClassSectionKeywords[Visibility];
NewCode:=Beauty.BeautifyKeyWord(SectionKeyWord);
FSourceChangeCache.Replace(gtNewLine,gtNewLine,
NewClassSectionInsertPos[Visibility],
NewClassSectionInsertPos[Visibility],
Beauty.GetIndentStr(NewClassSectionIndent[Visibility])+NewCode);
end;
begin
Result:=InsertClassHeaderComment;
if not Result then exit;
Result:=InsertMissingClassSemicolons;
if not Result then exit;
if FirstInsert=nil then begin
Result:=true;
exit;
end;
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
NewSectionKeyWordNeeded:=false;// 'published'/'public' keyword after first private section needed
if CodeCompleteClassNode.Desc = ctnClass then
NewSection := pcsPublished
else
NewSection := pcsPublic;
AddClassSection(pcsPrivate);
InsertNewClassParts(ncpPrivateVars);
InsertNewClassParts(ncpPrivateProcs);
AddClassSection(pcsProtected);
InsertNewClassParts(ncpProtectedVars);
InsertNewClassParts(ncpProtectedProcs);
if NewSectionKeyWordNeeded and (NewSection = pcsPublic) then begin
FSourceChangeCache.Replace(gtNewLine,gtNewLine,
NewClassSectionInsertPos[NewSection],
NewClassSectionInsertPos[NewSection],
Beauty.GetIndentStr(NewClassSectionIndent[NewSection])+
Beauty.BeautifyKeyWord(PascalClassSectionKeywords[NewSection]));
end
else
AddClassSection(pcsPublic);
InsertNewClassParts(ncpPublicVars);
InsertNewClassParts(ncpPublicProcs);
if NewSectionKeyWordNeeded and (NewSection = pcsPublished) then begin
FSourceChangeCache.Replace(gtNewLine,gtNewLine,
NewClassSectionInsertPos[NewSection],
NewClassSectionInsertPos[NewSection],
Beauty.GetIndentStr(NewClassSectionIndent[NewSection])+
Beauty.BeautifyKeyWord(PascalClassSectionKeywords[NewSection]));
end;
InsertNewClassParts(ncpPublishedVars);
InsertNewClassParts(ncpPublishedProcs);
Result:=true;
end;
function TCodeCompletionCodeTool.InsertClassHeaderComment: boolean;
var
ClassNode: TCodeTreeNode;
ClassIdentifierNode: TCodeTreeNode;
Code: String;
InsertPos: LongInt;
Indent: LongInt;
StartPos, CommentStart, CommentEnd: TCodeXYPosition;
Beauty: TBeautifyCodeOptions;
begin
Result:=true;
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
if not Beauty.ClassHeaderComments then exit;
// check if there is already a comment in front of the class
// find the start of the class (the position in front of the class name)
ClassNode:=CodeCompleteClassNode;
if ClassNode=nil then exit;
ClassIdentifierNode:=
ClassNode.GetNodeOfTypes([ctnTypeDefinition,ctnGenericType]);
if ClassIdentifierNode=nil then begin
DebugLn('TCodeCompletionCodeTool.InsertClassHeaderComment WARNING: class without name', ClassNode.DescAsString);
exit;
end;
if not CleanPosToCaret(ClassIdentifierNode.StartPos,StartPos) then exit;
Code:=ExtractDefinitionName(ClassIdentifierNode);
// check if there is already a comment in front
if FindCommentInFront(StartPos,Code,false,true,false,false,true,true,
CommentStart,CommentEnd)
then
// comment already exists
exit;
if CommentStart.Code=nil then ;
if CommentEnd.Code=nil then ;
// insert comment in front
InsertPos:=ClassIdentifierNode.StartPos;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
Code:=Beauty.GetIndentStr(Indent)+'{ '+Code+' }';
FSourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
InsertPos,InsertPos,Code);
end;
function TCodeCompletionCodeTool.InsertMissingClassSemicolons: boolean;
var
ANode: TCodeTreeNode;
ProcCode: String;
begin
Result:=false;
ANode:=FCompletingFirstEntryNode;
while (ANode<>nil) do begin
if ANode.Desc=ctnProcedure then begin
if ANode.FirstChild=nil then begin
debugln(['TCodeCompletionCodeTool.InsertMissingClassSemicolons warning: broken proc node: ',CleanPosToStr(ANode.StartPos)]);
exit;
end;
ProcCode:=ExtractProcHead(ANode,[phpWithStart,
phpWithoutClassKeyword,
phpWithVarModifiers,phpWithParameterNames,phpWithResultType,
phpWithProcModifiers,phpDoNotAddSemicolon]);
if (ProcCode<>'') and (ProcCode[length(ProcCode)]<>';') then begin
// add missing semicolon at end of procedure head
UndoReadNextAtom;
{$IFDEF VerboseCompletionAdds}
debugln(['TCodeCompletionCodeTool.InsertMissingClassSemicolons add missing semicolon at end of procedure head ProcCode="',dbgstr(ProcCode),'"']);
{$ENDIF}
if not FSourceChangeCache.Replace(gtNone,gtNone,
CurPos.EndPos,CurPos.EndPos,';') then
RaiseException(20170421201801,'InsertMissingClassSemicolons: unable to insert semicolon');
end;
MoveCursorToFirstProcSpecifier(ANode);
if (CurPos.Flag<>cafSemicolon) and (CurPos.EndPos<ANode.FirstChild.EndPos)
and (LastAtoms.HasPrior)
then begin
// add missing semicolon in front of proc modifiers
UndoReadNextAtom;
{$IFDEF VerboseCompletionAdds}
debugln(['TCodeCompletionCodeTool.InsertMissingClassSemicolons add missing semicolon in front of proc modifiers ProcCode="',dbgstr(ProcCode),'"']);
{$ENDIF}
if not FSourceChangeCache.Replace(gtNone,gtNone,
CurPos.EndPos,CurPos.EndPos,';') then
RaiseException(20170421201804,'InsertMissingClassSemicolons: unable to insert semicolon');
end;
end;
// next node
if ANode.NextBrother<>nil then begin
ANode:=ANode.NextBrother;
end else begin
ANode:=ANode.Parent.NextBrother;
while (ANode<>nil) and (ANode.Desc in (AllCodeSections+AllClassSections))
and (ANode.FirstChild=nil) do
ANode:=ANode.NextBrother;
if ANode<>nil then ANode:=ANode.FirstChild;
end;
end;
Result:=true;
end;
function TCodeCompletionCodeTool.InsertAllNewUnitsToMainUsesSection: boolean;
var
UsesNode: TCodeTreeNode;
AVLNode: TAVLTreeNode;
CurSourceName: String;
SectionNode: TCodeTreeNode;
NewUsesTerm: String;
NewUnitName: String;
InsertPos: LongInt;
begin
Result:=true;
if (fNewMainUsesSectionUnits=nil) then exit;
//DebugLn(['TCodeCompletionCodeTool.InsertAllNewUnitsToMainUsesSection ']);
UsesNode:=FindMainUsesNode;
// remove units, that are already in the uses section
CurSourceName:=GetSourceName(false);
RemoveNewMainUsesSectionUnit(PChar(CurSourceName)); // the unit itself
if UsesNode<>nil then begin
MoveCursorToNodeStart(UsesNode);
ReadNextAtom; // read 'uses'
repeat
ReadNextAtom; // read name
if AtomIsChar(';') then break;
RemoveNewMainUsesSectionUnit(@Src[CurPos.StartPos]);
if fNewMainUsesSectionUnits.Count=0 then exit;
ReadNextAtom;
if UpAtomIs('IN') then begin
ReadNextAtom;
ReadNextAtom;
end;
while AtomIsChar('.') do
begin
ReadNextAtom;
ReadNextAtom;
end;
if AtomIsChar(';') then break;
if not AtomIsChar(',') then break;
until (CurPos.StartPos>SrcLen);
end;
// add units
NewUsesTerm:='';
AVLNode:=fNewMainUsesSectionUnits.FindLowest;
while AVLNode<>nil do begin
if NewUsesTerm<>'' then
NewUsesTerm:=NewUsesTerm+', ';
NewUnitName:=GetIdentifier(PChar(AVLNode.Data));
//DebugLn(['TCodeCompletionCodeTool.InsertAllNewUnitsToMainUsesSection NewUnitName=',NewUnitName]);
NewUsesTerm:=NewUsesTerm+NewUnitName;
AVLNode:=fNewMainUsesSectionUnits.FindSuccessor(AVLNode);
end;
if UsesNode<>nil then begin
// add unit to existing uses section
MoveCursorToNodeStart(UsesNode); // for nice error position
InsertPos:=UsesNode.EndPos-1; // position of semicolon at end of uses section
NewUsesTerm:=', '+NewUsesTerm;
if not FSourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertPos,
NewUsesTerm) then exit;
end else begin
// create a new uses section
if Tree.Root=nil then exit;
SectionNode:=Tree.Root;
MoveCursorToNodeStart(SectionNode);
ReadNextAtom;
if UpAtomIs('UNIT') then begin
// search interface
SectionNode:=SectionNode.NextBrother;
if (SectionNode=nil) or (SectionNode.Desc<>ctnInterface) then exit;
MoveCursorToNodeStart(SectionNode);
ReadNextAtom;
end;
InsertPos:=CurPos.EndPos;
NewUsesTerm:=FSourceChangeCache.BeautifyCodeOptions.BeautifyKeyWord('uses')
+' '+NewUsesTerm+';';
if not FSourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
InsertPos,InsertPos,NewUsesTerm) then exit;
end;
end;
function TCodeCompletionCodeTool.FindClassMethodsComment(StartPos: integer; out
CommentStart, CommentEnd: integer): boolean;
var
Code: String;
begin
Result:=false;
Code:=ExtractClassName(CodeCompleteClassNode,false);
// search the comment
Result:=FindCommentInFront(StartPos,Code,false,false,false,true,true,
CommentStart,CommentEnd)
end;
procedure TCodeCompletionCodeTool.AddNewPropertyAccessMethodsToClassProcs(
ClassProcs: TAVLTree; const TheClassName: string);
var ANodeExt: TCodeTreeNodeExtension;
NewNodeExt: TCodeTreeNodeExtension;
Beauty: TBeautifyCodeOptions;
begin
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.AddNewPropertyAccessMethodsToClassProcs]');
{$ENDIF}
// add new property access methods to ClassProcs
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
ANodeExt:=FirstInsert;
while ANodeExt<>nil do begin
if not NodeExtIsVariable(ANodeExt) then begin
if FindNodeExtInTree(ClassProcs,ANodeExt.Txt)=nil then begin
NewNodeExt:=TCodeTreeNodeExtension.Create;
with NewNodeExt do begin
Txt:=UpperCaseStr(TheClassName)+'.'+ANodeExt.Txt; // Name+ParamTypeList
ExtTxt1:=Beauty.AddClassAndNameToProc(
ANodeExt.ExtTxt1,TheClassName,''); // complete proc head code
ExtTxt3:=ANodeExt.ExtTxt3;
Position:=ANodeExt.Position;
{$IFDEF CTDEBUG}
DebugLn(' Txt="',Txt,'"');
DebugLn(' ExtTxt1="',ExtTxt1,'"');
DebugLn(' ExtTxt3="',ExtTxt3,'"');
{$ENDIF}
end;
ClassProcs.Add(NewNodeExt);
end;
end;
ANodeExt:=ANodeExt.Next;
end;
end;
function TCodeCompletionCodeTool.UpdateProcBodySignature(
ProcBodyNodes: TAVLTree; const BodyNodeExt: TCodeTreeNodeExtension;
ProcAttrCopyDefToBody: TProcHeadAttributes; var ProcsCopied: boolean;
CaseSensitive: boolean): boolean;
var
OldProcCode: String;
NewProcCode: String;
InsertEndPos: LongInt;
BodyProcHeadNode: TCodeTreeNode;
Indent: LongInt;
InsertPos: LongInt;
DefNodeExt: TCodeTreeNodeExtension;
Beauty: TBeautifyCodeOptions;
begin
Result:=true;
DefNodeExt:=TCodeTreeNodeExtension(BodyNodeExt.Data);
if DefNodeExt=nil then exit;
// this body has a definition
// compare body and definition
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
NewProcCode:=ExtractProcHead(DefNodeExt.Node, ProcAttrCopyDefToBody);
BodyProcHeadNode:=BodyNodeExt.Node.FirstChild;
InsertPos:=BodyNodeExt.Node.StartPos;
InsertEndPos:=BodyProcHeadNode.EndPos;
Indent:=Beauty.GetLineIndent(Src, InsertPos);
NewProcCode:=Beauty.BeautifyProc(NewProcCode, Indent, false);
OldProcCode:=ExtractProcHead(BodyNodeExt.Node, ProcAttrCopyDefToBody);
if CompareTextIgnoringSpace(NewProcCode, OldProcCode, CaseSensitive)<>0 then begin
// update body
//debugln(['TCodeCompletionCodeTool.UpdateProcBodySignatures Old="',dbgstr(OldProcCode),'" New="',dbgstr(NewProcCode),'"']);
ProcsCopied:=true;
if not FSourceChangeCache.Replace(gtNone, gtNone, InsertPos,
InsertEndPos, NewProcCode) then
exit(false);
end;
// update body signature in tree,
// so that no new body is created for this definition
ProcBodyNodes.RemovePointer(BodyNodeExt);
BodyNodeExt.Txt:=DefNodeExt.Txt;
ProcBodyNodes.Add(BodyNodeExt);
end;
procedure TCodeCompletionCodeTool.CheckForOverrideAndAddInheritedCode(
ANodeExt: TCodeTreeNodeExtension; Indent: integer);
// check for 'override' directive and add 'inherited' code to body
var
ProcCode, ProcCall: string;
ProcNode, ClassNode: TCodeTreeNode;
i: integer;
InclProcCall: Boolean;
Beauty: TBeautifyCodeOptions;
Params: TFindDeclarationParams;
Tool: TFindDeclarationTool;
begin
if not AddInheritedCodeToOverrideMethod then exit;
{$IFDEF CTDEBUG}
DebugLn('[TCodeCompletionCodeTool.CheckForOverrideAndAddInheritedCode]');
{$ENDIF}
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
ProcNode:=ANodeExt.Node;
if (ProcNode=nil) and (ANodeExt.ExtTxt3<>'') then Exit;
InclProcCall:=False;
if (ProcNodeHasSpecifier(ProcNode,psOVERRIDE)) then begin
// Check for ancestor abstract method.
Params:=TFindDeclarationParams.Create;
try
ClassNode:=CodeCompleteClassNode;
Tool:=Self;
while Tool.FindAncestorOfClass(ClassNode,Params,True) do begin
Tool:=Params.NewCodeTool;
ClassNode:=Params.NewNode;
Params.ContextNode:=ClassNode;
Params.IdentifierTool:=Self;
// FirstChild skips keywords 'procedure' or 'function' or 'class procedure'
Params.SetIdentifier(Self,@Src[ProcNode.FirstChild.StartPos],nil);
if Tool.FindIdentifierInContext(Params) then begin
// Found ancestor definition.
if (Params.NewNode<>nil)
and (Params.NewNode.Desc in [ctnProcedure,ctnProcedureHead]) then
InclProcCall:=not Tool.ProcNodeHasSpecifier(Params.NewNode,psABSTRACT);
Break;
end;
end;
finally
Params.Free;
end;
if InclProcCall then begin
ProcCode:=ExtractProcHead(ProcNode,[phpWithStart,phpAddClassname,
phpWithVarModifiers,phpWithParameterNames,
phpWithResultType,phpWithCallingSpecs]);
ProcCall:='inherited '+ExtractProcHead(ProcNode,[phpWithoutClassName,
phpWithParameterNames,phpWithoutParamTypes]);
for i:=1 to length(ProcCall)-1 do
if ProcCall[i]=';' then
ProcCall[i]:=',';
if ProcCall[length(ProcCall)]<>';' then
ProcCall:=ProcCall+';';
if NodeIsFunction(ProcNode) then
ProcCall:=Beauty.BeautifyIdentifier('Result')+':='+ProcCall;
ProcCode:=ProcCode+Beauty.LineEnd+'begin'+Beauty.LineEnd
+Beauty.GetIndentStr(Beauty.Indent)+ProcCall+Beauty.LineEnd+'end;';
ProcCode:=Beauty.BeautifyProc(ProcCode,Indent,false);
ANodeExt.ExtTxt3:=ProcCode;
end;
end;
end;
function TCodeCompletionCodeTool.UpdateProcBodySignatures(ProcDefNodes,
ProcBodyNodes: TAVLTree; ProcAttrCopyDefToBody: TProcHeadAttributes; out
ProcsCopied: boolean; OnlyNode: TCodeTreeNode): boolean;
{ ProcDefNodes and ProcBodyNodes were created by GatherProcNodes
trees of TCodeTreeNodeExtension sorted with CompareCodeTreeNodeExt
NodexExt.Data has mapping to ProcBodyNodes extensions, see GuessMethodDefBodyMapping
Node.Desc = ctnProcedure
Node.Txt = ExtractProcHead(Node,SomeAttributes)
}
var
BodyAVLNode: TAVLTreeNode;
BodyNodeExt: TCodeTreeNodeExtension;
Bodies: TFPList;
i: Integer;
DefNodeExt: TCodeTreeNodeExtension;
begin
Result:=true;
ProcsCopied:=false;
Bodies:=nil;
try
// replace body proc head(s) with def proc head(s)
Bodies:=TFPList.Create;
BodyAVLNode:=ProcBodyNodes.FindLowest;
while BodyAVLNode<>nil do begin
BodyNodeExt:=TCodeTreeNodeExtension(BodyAVLNode.Data);
BodyAVLNode:=ProcBodyNodes.FindSuccessor(BodyAVLNode);
DefNodeExt:=TCodeTreeNodeExtension(BodyNodeExt.Data);
if DefNodeExt=nil then continue;
if (OnlyNode=nil) or (OnlyNode=DefNodeExt.Node)
or (OnlyNode.HasAsParent(DefNodeExt.Node)) then
Bodies.Add(BodyNodeExt);
end;
for i:=0 to Bodies.Count-1 do begin
BodyNodeExt:=TCodeTreeNodeExtension(Bodies[i]);
if not UpdateProcBodySignature(ProcBodyNodes, BodyNodeExt,
ProcAttrCopyDefToBody, ProcsCopied,
FSourceChangeCache.BeautifyCodeOptions.UpdateOtherProcSignaturesCase)
then
exit(false);
end;
finally
FreeAndNil(Bodies);
ClearNodeExtData(ProcBodyNodes);
ClearNodeExtData(ProcDefNodes);
end;
end;
procedure TCodeCompletionCodeTool.GuessProcDefBodyMapping(ProcDefNodes,
ProcBodyNodes: TAVLTree; MapByNameOnly, MapLastOne: boolean);
{ ProcDefNodes and ProcBodyNodes are trees of TCodeTreeNodeExtension
ProcDefNodes Data points to mapped ProcBodyNodes nodes
}
procedure MapBodiesAndDefsByNameAndParams;
var
BodyAVLNode: TAVLTreeNode;
BodyNodeExt: TCodeTreeNodeExtension;
DefAVLNode: TAVLTreeNode;
begin
BodyAVLNode:=ProcBodyNodes.FindLowest;
while BodyAVLNode<>nil do begin
BodyNodeExt:=TCodeTreeNodeExtension(BodyAVLNode.Data);
if BodyNodeExt.Data=nil then begin
DefAVLNode:=ProcDefNodes.Find(BodyNodeExt);
if DefAVLNode<>nil then begin
// exact match => connect
BodyNodeExt.Data:=DefAVLNode.Data;
TCodeTreeNodeExtension(DefAVLNode.Data).Data:=BodyNodeExt;
end else begin
{$IFDEF VerboseUpdateProcBodySignatures}
debugln([' MapBodiesAndDefsByNameAndParams has no exact match definition: '+BodyNodeExt.Txt]);
{$ENDIF}
end;
end;
BodyAVLNode:=ProcBodyNodes.FindSuccessor(BodyAVLNode);
end;
end;
function CreateNameTree(NodeExtTree: TAVLTree; SkipNodesWithData: boolean): TAVLTree;
var
AVLNodeExt: TAVLTreeNode;
ProcNode: TCodeTreeNode;
NodeExt: TCodeTreeNodeExtension;
NewNodeExt: TCodeTreeNodeExtension;
begin
Result:=nil;
if NodeExtTree=nil then exit;
AVLNodeExt:=NodeExtTree.FindLowest;
while AVLNodeExt<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNodeExt.Data);
AVLNodeExt:=NodeExtTree.FindSuccessor(AVLNodeExt);
if (not SkipNodesWithData) or (NodeExt.Data=nil)
or (ProcNodeHasSpecifier(NodeExt.Node,psEXTERNAL)) then begin
{$IFDEF VerboseUpdateProcBodySignatures}
if NodeExtTree=ProcBodyNodes then
debugln(['CreateNameTree body without corresponding def: ',NodeExt.Txt])
else
debugln(['CreateNameTree def without corresponding body: ',NodeExt.Txt]);
{$ENDIF}
ProcNode:=NodeExt.Node;
NewNodeExt:=TCodeTreeNodeExtension.Create;
NewNodeExt.Node:=ProcNode;
NewNodeExt.Txt:=ExtractProcName(ProcNode,[phpWithoutClassName]);
NewNodeExt.Data:=NodeExt;
NewNodeExt.Flags:=Integer(ExtractProcedureGroup(ProcNode));
if Result=nil then
Result:=TAVLTree.Create(@CompareCodeTreeNodeExtMethodHeaders);
Result.Add(NewNodeExt);
end;
end;
end;
procedure MapBodiesAndDefsByName;
var
BodyNodesByName, DefNodesByName: TAVLTree;
BodyAVLNode: TAVLTreeNode;
LastBodySameName: TAVLTreeNode;
FirstDefSameName: TAVLTreeNode;
LastDefSameName: TAVLTreeNode;
ProcBodyExt: TCodeTreeNodeExtension;
DefExt: TCodeTreeNodeExtension;
DefNameExt: TCodeTreeNodeExtension;
ProcBodyByNameExt: TCodeTreeNodeExtension;
begin
BodyNodesByName:=nil;
DefNodesByName:=nil;
try
// create a tree of proc names and nodes, that were not yet mapped
// one for the bodies ...
BodyNodesByName:=CreateNameTree(ProcBodyNodes,true);
if BodyNodesByName=nil then exit;
// ... and one for the definitions
DefNodesByName:=CreateNameTree(ProcDefNodes,true);
if DefNodesByName=nil then exit;
// check each body if it can be mapped bijective by name
BodyAVLNode:=BodyNodesByName.FindLowest;
while BodyAVLNode<>nil do begin
ProcBodyByNameExt:=TCodeTreeNodeExtension(BodyAVLNode.Data);
ProcBodyExt:=TCodeTreeNodeExtension(ProcBodyByNameExt.Data);
LastBodySameName:=BodyNodesByName.FindRightMostSameKey(BodyAVLNode);
if LastBodySameName<>BodyAVLNode then begin
// multiple bodies with same name => skip
{$IFDEF VerboseUpdateProcBodySignatures}
debugln([' MapBodiesAndDefsByName multiple definitionless bodies with same name:']);
repeat
ProcBodyByNameExt:=TCodeTreeNodeExtension(BodyAVLNode.Data);
ProcBodyExt:=TCodeTreeNodeExtension(ProcBodyByNameExt.Data);
debugln([' '+ProcBodyExt.Txt]);
BodyAVLNode:=BodyNodesByName.FindSuccessor(BodyAVLNode);
until BodyAVLNode<>LastBodySameName;
{$ENDIF}
BodyAVLNode:=LastBodySameName;
end else begin
// there is only one body with this name that has no exact definition
// => search in definitions
FirstDefSameName:=DefNodesByName.FindLeftMost(ProcBodyByNameExt);
if FirstDefSameName<>nil then begin
// there is at least one definition with this name and without a body
DefNameExt:=TCodeTreeNodeExtension(FirstDefSameName.Data);
DefExt:=TCodeTreeNodeExtension(DefNameExt.Data);
LastDefSameName:=DefNodesByName.FindRightMostSameKey(FirstDefSameName);
if LastDefSameName=FirstDefSameName then begin
// there is exactly one definition with this name and without a body
// => connect
ProcBodyExt.Data:=DefExt;
DefExt.Data:=ProcBodyExt;
end else begin
{$IFDEF VerboseUpdateProcBodySignatures}
debugln([' MapBodiesAndDefsByName multiple bodyless definitions with same name: ']);
repeat
DefNameExt:=TCodeTreeNodeExtension(FirstDefSameName.Data);
DefExt:=TCodeTreeNodeExtension(DefNameExt.Data);
debugln([' '+DefExt.Txt]);
FirstDefSameName:=DefNodesByName.FindSuccessor(FirstDefSameName);
until FirstDefSameName=LastDefSameName;
{$ENDIF}
end;
end;
end;
BodyAVLNode:=BodyNodesByName.FindSuccessor(BodyAVLNode);
end;
finally
if BodyNodesByName<>nil then begin
BodyNodesByName.FreeAndClear;
BodyNodesByName.Free;
end;
if DefNodesByName<>nil then begin
DefNodesByName.FreeAndClear;
DefNodesByName.Free;
end;
end;
end;
function GetNodeExtWithoutData(Tree: TAVLTree; out Count: integer
): TCodeTreeNodeExtension;
var
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
Result:=nil;
Count:=0;
AVLNode:=Tree.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Data=nil then begin
inc(Count);
Result:=NodeExt;
end;
AVLNode:=Tree.FindSuccessor(AVLNode);
end;
end;
procedure MapLastBodyAndDef;
var
BodyNodeExt: TCodeTreeNodeExtension;
Cnt: integer;
DefNodeExt: TCodeTreeNodeExtension;
begin
BodyNodeExt:=GetNodeExtWithoutData(ProcBodyNodes,Cnt);
if Cnt>1 then debugln(['Note: TCodeCompletionCodeTool.UpdateProcBodySignatures.MapLastBodyAndDef multiple bodies which can not be mapped to definitions']);
if Cnt<>1 then exit;
DefNodeExt:=GetNodeExtWithoutData(ProcDefNodes,Cnt);
if Cnt>1 then debugln(['Note: TCodeCompletionCodeTool.UpdateProcBodySignatures.MapLastBodyAndDef multiple definitions which can not be mapped to bodies']);
if Cnt<>1 then exit;
BodyNodeExt.Data:=DefNodeExt;
DefNodeExt.Data:=BodyNodeExt;
end;
begin
{$IFDEF VerboseUpdateProcBodySignatures}
debugln(['TCodeCompletionCodeTool.GuessProcDefBodyMapping',
' ProcDefNodes=',ProcDefNodes.Count,
' ProcBodyNodes=',ProcBodyNodes.Count,
' MapByNameOnly=',MapByNameOnly,
' MapLastOne=',MapLastOne
]);
{$ENDIF}
ClearNodeExtData(ProcBodyNodes);
ClearNodeExtData(ProcDefNodes);
MapBodiesAndDefsByNameAndParams; // first: map all exact matches between bodies and defs
if MapByNameOnly then
MapBodiesAndDefsByName; // second: map remaining by name without params
if MapLastOne then
MapLastBodyAndDef; // last: map if there is exactly one unmatching body and def
end;
function TCodeCompletionCodeTool.GatherClassProcDefinitions(
ClassNode: TCodeTreeNode; RemoveAbstracts: boolean): TAVLTree;
var
AnAVLNode: TAVLTreeNode;
NextAVLNode: TAVLTreeNode;
ANodeExt: TCodeTreeNodeExtension;
ANode: TCodeTreeNode;
begin
Result:=GatherProcNodes(ClassNode.FirstChild,
[phpInUpperCase,phpAddClassName],ExtractClassName(ClassNode,true));
if RemoveAbstracts then begin
AnAVLNode:=Result.FindLowest;
while AnAVLNode<>nil do begin
NextAVLNode:=Result.FindSuccessor(AnAVLNode);
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
ANode:=ANodeExt.Node;
if (ANode<>nil) and (ANode.Desc=ctnProcedure)
and ProcNodeHasSpecifier(ANode,psABSTRACT) then begin
Result.Delete(AnAVLNode);
ANodeExt.Free;
end;
AnAVLNode:=NextAVLNode;
end;
end;
end;
function TCodeCompletionCodeTool.GatherClassProcBodies(ClassNode: TCodeTreeNode
): TAVLTree;
var
TypeSectionNode: TCodeTreeNode;
begin
TypeSectionNode:=ClassNode.GetTopMostNodeOfType(ctnTypeSection);
Result:=GatherProcNodes(TypeSectionNode,
[phpInUpperCase,phpIgnoreForwards,phpOnlyWithClassname],
ExtractClassName(ClassNode,true,true,false));
end;
function TCodeCompletionCodeTool.CreateMissingClassProcBodies(
UpdateSignatures: boolean): boolean;
const
ProcAttrDefToBody = [phpWithStart,
phpAddClassname,phpWithVarModifiers,
phpWithParameterNames,phpWithResultType,
phpWithCallingSpecs,phpWithAssembler];
var
TheClassName: string;
Beauty: TBeautifyCodeOptions;
procedure InsertProcBody(ANodeExt: TCodeTreeNodeExtension;
InsertPos, Indent: integer);
var ProcCode: string;
begin
if ANodeExt.ExtTxt3<>'' then
ProcCode:=ANodeExt.ExtTxt3
else
ProcCode:=ANodeExt.ExtTxt1;
ProcCode:=Beauty.AddClassAndNameToProc(ProcCode,TheClassName,'');
{$IFDEF CTDEBUG}
DebugLn('CreateMissingClassProcBodies InsertProcBody ',TheClassName,' "',ProcCode,'"');
{$ENDIF}
ProcCode:=Beauty.BeautifyProc(ProcCode,Indent,ANodeExt.ExtTxt3='');
FSourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,ProcCode);
if FJumpToProcHead.Name='' then begin
// remember one proc body to jump to after the completion
FJumpToProcHead.Name:=ANodeExt.Txt;
FJumpToProcHead.Group:=TPascalMethodGroup(ANodeExt.Flags);
FJumpToProcHead.ResultType:=ANodeExt.ExtTxt4;
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 FJumpToProcHead.Name="',FJumpToProcHead.Name,'"');
{$ENDIF}
end;
end;
procedure CreateCodeForMissingProcBody(TheNodeExt: TCodeTreeNodeExtension;
Indent: integer);
var
ANode: TCodeTreeNode;
ProcCode: string;
begin
CheckForOverrideAndAddInheritedCode(TheNodeExt,Indent);
if (TheNodeExt.ExtTxt1='') and (TheNodeExt.ExtTxt3='') then begin
ANode:=TheNodeExt.Node;
if (ANode<>nil) and (ANode.Desc=ctnProcedure) then begin
ProcCode:=ExtractProcHead(ANode,ProcAttrDefToBody);
//debugln(['CreateCodeForMissingProcBody Definition="',ProcCode,'"']);
TheNodeExt.ExtTxt3:=Beauty.BeautifyProc(ProcCode,Indent,true);
//debugln(['CreateCodeForMissingProcBody Beautified="',TheNodeExt.ExtTxt3,'"']);
end;
end;
end;
var
ProcBodyNodes, ClassProcs: TAVLTree;
ANodeExt, ANodeExt2: TCodeTreeNodeExtension;
ExistingNode, MissingNode, AnAVLNode, NextAVLNode,
NearestAVLNode: TAVLTreeNode;
cmp, MissingNodePosition: integer;
FirstExistingProcBody, LastExistingProcBody, ImplementationNode,
ANode, ANode2: TCodeTreeNode;
ClassStartComment, s: string;
Caret1, Caret2: TCodeXYPosition;
MethodInsertPolicy: TMethodInsertPolicy;
NearestNodeValid: boolean;
procedure FindTopMostAndBottomMostProcBodies;
begin
ExistingNode:=ProcBodyNodes.FindLowest;
if ExistingNode<>nil then
LastExistingProcBody:=TCodeTreeNodeExtension(ExistingNode.Data).Node
else
LastExistingProcBody:=nil;
FirstExistingProcBody:=LastExistingProcBody;
while ExistingNode<>nil do begin
ANode:=TCodeTreeNodeExtension(ExistingNode.Data).Node;
if ANode.StartPos<FirstExistingProcBody.StartPos then
FirstExistingProcBody:=ANode;
if ANode.StartPos>LastExistingProcBody.StartPos then
LastExistingProcBody:=ANode;
//DebugLn(['FindTopMostAndBottomMostProcBodies ',TCodeTreeNodeExtension(ExistingNode.Data).Txt]);
ExistingNode:=ProcBodyNodes.FindSuccessor(ExistingNode);
end;
end;
procedure CheckForDoubleDefinedMethods;
begin
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
NextAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
if NextAVLNode<>nil then begin
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
ANodeExt2:=TCodeTreeNodeExtension(NextAVLNode.Data);
if CompareCodeTreeNodeExtMethodHeaders(ANodeExt, ANodeExt2) = 0 then
begin
// proc redefined -> error
if ANodeExt.Node.StartPos>ANodeExt2.Node.StartPos then begin
ANode:=ANodeExt.Node;
ANode2:=ANodeExt2.Node;
end else begin
ANode:=ANodeExt2.Node;
ANode2:=ANodeExt.Node;
end;
debugln(['CheckForDoubleDefinedMethods redefined']);
debugln(' 1. ',ANodeExt.Txt,' ',CleanPosToStr(ANodeExt.Node.StartPos));
debugln(' 2. ',ANodeExt2.Txt,' ',CleanPosToStr(ANodeExt2.Node.StartPos));
CleanPosToCaret(ANode.FirstChild.StartPos,Caret1);
CleanPosToCaret(ANode2.FirstChild.StartPos,Caret2);
s:=IntToStr(Caret2.Y)+','+IntToStr(Caret2.X);
if Caret1.Code<>Caret2.Code then
s:=s+' in '+CreateRelativePath(Caret2.Code.Filename,ExtractFilePath(Caret1.Code.Filename));
MoveCursorToNodeStart(ANode.FirstChild);
RaiseException(20170421201808,'procedure redefined (first at '+s+')');
end;
end;
AnAVLNode:=NextAVLNode;
end;
end;
procedure FindInsertPointForNewClass(out InsertPos, Indent: LongInt);
procedure SetIndentAndInsertPos(Node: TCodeTreeNode; Behind: boolean);
begin
Indent:=Beauty.GetLineIndent(Src,Node.StartPos);
if Behind then
InsertPos:=FindLineEndOrCodeAfterPosition(Node.EndPos)
else
InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
end;
var
StartSearchProc: TCodeTreeNode;
NearestProcNode: TCodeTreeNode;
UnitInterfaceNode: TCodeTreeNode;
begin
InsertPos:=0;
Indent:=0;
ImplementationNode:=FindImplementationNode;
StartSearchProc:=nil;
UnitInterfaceNode:=FindInterfaceNode;
if (UnitInterfaceNode<>nil)
and CodeCompleteClassNode.HasAsParent(UnitInterfaceNode) then begin
// class is in interface section
// -> insert at the end of the implementation section
if ImplementationNode=nil then begin
// create implementation section
InsertPos:=UnitInterfaceNode.EndPos;
Indent:=Beauty.GetLineIndent(Src,UnitInterfaceNode.StartPos);
if not CodeCompleteSrcChgCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
CodeCompleteSrcChgCache.BeautifyCodeOptions.BeautifyKeyWord('implementation'))
then begin
MoveCursorToCleanPos(InsertPos);
RaiseException(20170421201812,'unable to insert implementation section (read only?)');
end;
exit;
end else if (ImplementationNode.FirstChild=nil)
or (ImplementationNode.FirstChild.Desc=ctnBeginBlock) then begin
// implementation is empty
Indent:=Beauty.GetLineIndent(Src,ImplementationNode.StartPos);
if ImplementationNode.FirstChild<>nil then
InsertPos:=ImplementationNode.FirstChild.StartPos
else
InsertPos:=ImplementationNode.EndPos;
exit;
end;
StartSearchProc:=ImplementationNode.FirstChild;
end else begin
// class is not in interface section
StartSearchProc:=CodeCompleteClassNode.GetTopMostNodeOfType(ctnTypeSection);
end;
case Beauty.ForwardProcBodyInsertPolicy of
fpipInFrontOfMethods:
begin
// Try to insert new proc in front of existing methods
// find first method
NearestProcNode:=StartSearchProc;
while (NearestProcNode<>nil) and (NearestProcNode.Desc<>ctnBeginBlock)
and (not NodeIsMethodBody(NearestProcNode)) do
NearestProcNode:=NearestProcNode.NextBrother;
if NearestProcNode<>nil then begin
// the comments in front of the first method probably belong to the class
// Therefore insert behind the node in front of the first method
Indent:=Beauty.GetLineIndent(Src,NearestProcNode.StartPos);
if NearestProcNode.PriorBrother<>nil then begin
InsertPos:=FindLineEndOrCodeAfterPosition(NearestProcNode.PriorBrother.EndPos);
end else begin
InsertPos:=NearestProcNode.Parent.StartPos;
while (InsertPos<=NearestProcNode.StartPos)
and (not IsSpaceChar[Src[InsertPos]]) do
inc(InsertPos);
end;
InsertPos:=SkipResourceDirective(InsertPos);
exit;
end;
end;
fpipBehindMethods:
begin
// Try to insert new proc behind existing methods
// find last method (go to last brother and search backwards)
if (StartSearchProc<>nil) and (StartSearchProc.Parent<>nil) then
NearestProcNode:=StartSearchProc.Parent.LastChild
else
NearestProcNode:=nil;
while (NearestProcNode<>nil) and (not NodeIsMethodBody(NearestProcNode)) do
NearestProcNode:=NearestProcNode.PriorBrother;
if NearestProcNode<>nil then begin
SetIndentAndInsertPos(NearestProcNode,NearestProcNode.Desc<>ctnBeginBlock);
InsertPos:=SkipResourceDirective(InsertPos);
exit;
end;
end;
end;
// Default position: Insert behind last node
if (StartSearchProc<>nil)
and (StartSearchProc.Parent<>nil) then begin
NearestProcNode:=StartSearchProc.Parent.LastChild;
if NearestProcNode.Desc=ctnBeginBlock then
NearestProcNode:=NearestProcNode.PriorBrother;
end;
if NearestProcNode<>nil then begin
Indent:=0;
SetIndentAndInsertPos(NearestProcNode,true);
InsertPos:=SkipResourceDirective(InsertPos);
exit;
end;
RaiseException(20170421201815,'TCodeCompletionCodeTool.CreateMissingClassProcBodies.FindInsertPointForNewClass '
+' Internal Error: no insert position found');
end;
procedure InsertClassMethodsComment(InsertPos, Indent: integer);
var
CommentStartPos: integer;
CommentEndPos: integer;
begin
// insert class comment
if ClassProcs.Count=0 then exit;
if not Beauty.ClassImplementationComments
then
exit;
// find the start of the class (the position in front of the class name)
// check if there is already a comment in front
if FindClassMethodsComment(InsertPos,CommentStartPos,CommentEndPos) then begin
// comment already exists
exit;
end;
ClassStartComment:=Beauty.GetIndentStr(Indent)
+'{ '+ExtractClassName(CodeCompleteClassNode,false)+' }';
FSourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
ClassStartComment);
end;
var
InsertPos: integer;
Indent: integer;
ProcsCopied: boolean;
OnlyNode: TCodeTreeNode;
begin
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
DebugLn('TCodeCompletionCodeTool.CreateMissingClassProcBodies Gather existing method bodies ... ');
{$ENDIF}
if CodeCompleteClassNode.Desc in AllClassInterfaces then begin
// interfaces have no implementations
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies interface ',CodeCompleteClassNode.DescAsString]);
{$ENDIF}
exit(true);
end;
if FindClassExternalNode(CodeCompleteClassNode)<>nil then begin
// external class has no implementations
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies external ',CodeCompleteClassNode.DescAsString]);
{$ENDIF}
exit(true);
end;
Result:=false;
Beauty:=FSourceChangeCache.BeautifyCodeOptions;
MethodInsertPolicy:=Beauty.MethodInsertPolicy;
// gather existing class proc bodies
ClassProcs:=nil;
ProcBodyNodes:=nil;
try
{$IFDEF VerboseCreateMissingClassProcBodies}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies get class procs of ',CodeCompleteClassNode.DescAsString]);
{$ENDIF}
ClassProcs:=GatherClassProcDefinitions(CodeCompleteClassNode,true);
{$IFDEF VerboseCreateMissingClassProcBodies}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies get bodies of ',CodeCompleteClassNode.DescAsString]);
{$ENDIF}
ProcBodyNodes:=GatherClassProcBodies(CodeCompleteClassNode);
{$IFDEF VerboseCreateMissingClassProcBodies}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies ClassProcs=',ClassProcs.Count]);
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' Gathered ProcDef ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
end;
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies ProcBodyNodes=',ProcBodyNodes.Count]);
AnAVLNode:=ProcBodyNodes.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' Gathered ProcBody ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
AnAVLNode:=ProcBodyNodes.FindSuccessor(AnAVLNode);
end;
{$ENDIF}
// find topmost and bottommost proc body
FindTopMostAndBottomMostProcBodies;
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
DebugLn('TCodeCompletionCodeTool.CreateMissingClassProcBodies Gather existing method declarations ... ');
{$ENDIF}
TheClassName:=ExtractClassName(CodeCompleteClassNode,false,true,Scanner.CompilerMode in [cmDELPHI,cmDELPHIUNICODE]);
// check for double defined methods in ClassProcs
CheckForDoubleDefinedMethods;
// check for changed procs
if UpdateSignatures then begin
GuessProcDefBodyMapping(ClassProcs,ProcBodyNodes,true,true);
if Beauty.UpdateAllMethodSignatures then
OnlyNode:=nil
else
OnlyNode:=FCompletingCursorNode;
{$IFDEF VerboseCreateMissingClassProcBodies}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies Beauty.UpdateAllMethodSignatures=',Beauty.UpdateAllMethodSignatures,' ',OnlyNode<>nil]);
{$ENDIF}
if not UpdateProcBodySignatures(ClassProcs,ProcBodyNodes,ProcAttrDefToBody,
ProcsCopied,OnlyNode)
then exit;
end;
// there are new methods
CurNode:=FirstExistingProcBody;
{$IFDEF VerboseCreateMissingClassProcBodies}
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' SignaturesUpdated ProcDef ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
end;
{$ENDIF}
AddNewPropertyAccessMethodsToClassProcs(ClassProcs,TheClassName);
{$IFDEF VerboseCreateMissingClassProcBodies}
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' AfterPropsCompleted ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
end;
{$ENDIF}
if MethodInsertPolicy=mipClassOrder then begin
// insert in ClassOrder -> get a definition position for every method
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
if ANodeExt.Position<1 then
// position not set => this proc was already there => there is a node
ANodeExt.Position:=ANodeExt.Node.StartPos;
// find corresponding proc body
NextAVLNode:=ProcBodyNodes.Find(ANodeExt);
if NextAVLNode<>nil then begin
// NextAVLNode.Data is the TCodeTreeNodeExtension for the method body
// (note 1)
ANodeExt.Data:=NextAVLNode.Data;
end;
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
end;
// sort the method definitions with the definition position
ClassProcs.OnCompare:=@CompareCodeTreeNodeExtWithPos;
end;
{$IFDEF VerboseCreateMissingClassProcBodies}
AnAVLNode:=ClassProcs.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' BeforeAddMissing ProcDef "',TCodeTreeNodeExtension(AnAVLNode.Data).Txt,'"');
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
end;
AnAVLNode:=ProcBodyNodes.FindLowest;
while AnAVLNode<>nil do begin
DebugLn(' BeforeAddMissing ProcBody "',TCodeTreeNodeExtension(AnAVLNode.Data).Txt,'"');
AnAVLNode:=ProcBodyNodes.FindSuccessor(AnAVLNode);
end;
{$ENDIF}
// search for missing proc bodies
if (ProcBodyNodes.Count=0) then begin
// there were no old proc bodies of the class -> start class
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
DebugLn('TCodeCompletionCodeTool.CreateMissingClassProcBodies Starting class in implementation ');
{$ENDIF}
FindInsertPointForNewClass(InsertPos,Indent);
{$IFDEF VerboseCreateMissingClassProcBodies}
debugln(['TCodeCompletionCodeTool.CreateMissingClassProcBodies Indent=',Indent,' InsertPos=',dbgstr(copy(Src,InsertPos-10,10)),'|',dbgstr(copy(Src,InsertPos,10))]);
{$ENDIF}
InsertClassMethodsComment(InsertPos,Indent);
// insert all proc bodies
MissingNode:=ClassProcs.FindHighest;
while (MissingNode<>nil) do begin
ANodeExt:=TCodeTreeNodeExtension(MissingNode.Data);
MissingNode:=ClassProcs.FindPrecessor(MissingNode);
if ProcNodeHasSpecifier(ANodeExt.Node,psEXTERNAL) then continue;
CreateCodeForMissingProcBody(ANodeExt,Indent);
InsertProcBody(ANodeExt,InsertPos,Indent);
end;
end else begin
// there were old class procs already
// -> search a good Insert Position behind or in front of
// another proc body of this class
{$IF defined(CTDEBUG) or defined(VerboseCreateMissingClassProcBodies)}
DebugLn('TCodeCompletionCodeTool.CreateMissingClassProcBodies Insert missing bodies between existing ... ClassProcs.Count=',dbgs(ClassProcs.Count));
{$ENDIF}
// set default insert position
Indent:=Beauty.GetLineIndent(Src,LastExistingProcBody.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(LastExistingProcBody.EndPos);
// check for all defined class methods (MissingNode), if there is a body
MissingNode:=ClassProcs.FindHighest;
NearestNodeValid:=false;
while (MissingNode<>nil) do begin
ANodeExt:=TCodeTreeNodeExtension(MissingNode.Data);
MissingNode:=ClassProcs.FindPrecessor(MissingNode);
ExistingNode:=ProcBodyNodes.Find(ANodeExt);
{$IFDEF VerboseCreateMissingClassProcBodies}
DebugLn(['TCodeCompletionCodeTool.CreateMissingClassProcBodies ANodeExt.Txt=',ANodeExt.Txt,' ExistingNode=',ExistingNode<>nil]);
{$ENDIF}
if (ExistingNode=nil) and (not ProcNodeHasSpecifier(ANodeExt.Node,psEXTERNAL))
then begin
{$IFDEF VerboseCreateMissingClassProcBodies}
//generates AV:
//DebugLn(['TCodeCompletionCodeTool.CreateMissingClassProcBodies ANodeExt.Txt=',ANodeExt.Txt,' ExistingNode=',TCodeTreeNodeExtension(ExistingNode.Data).Txt]);
{$ENDIF}
// MissingNode does not have a body -> insert proc body
case MethodInsertPolicy of
mipAlphabetically:
begin
// search alphabetically nearest proc body
ExistingNode:=ProcBodyNodes.FindNearest(ANodeExt);
cmp:=CompareCodeTreeNodeExtMethodHeaders(ExistingNode.Data,ANodeExt);
if (cmp<0) then begin
AnAVLNode:=ProcBodyNodes.FindSuccessor(ExistingNode);
if AnAVLNode<>nil then begin
ExistingNode:=AnAVLNode;
cmp:=1;
end;
end;
ANodeExt2:=TCodeTreeNodeExtension(ExistingNode.Data);
ANode:=ANodeExt2.Node;
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
if cmp>0 then begin
// insert behind ExistingNode
InsertPos:=FindLineEndOrCodeAfterPosition(ANode.EndPos);
end else begin
// insert in front of ExistingNode
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
end;
end;
mipClassOrder:
begin
// search definition-position nearest proc node
MissingNodePosition:=ANodeExt.Position;
if not NearestNodeValid then begin
// search NearestAVLNode method with body in front of MissingNode
// and NextAVLNode method with body behind MissingNode
NearestAVLNode:=nil;
NextAVLNode:=ClassProcs.FindHighest;
NearestNodeValid:=true;
end;
while (NextAVLNode<>nil) do begin
ANodeExt2:=TCodeTreeNodeExtension(NextAVLNode.Data);
if ANodeExt2.Data<>nil then begin
// method has body
if ANodeExt2.Position>MissingNodePosition then
break;
NearestAVLNode:=NextAVLNode;
end;
NextAVLNode:=ClassProcs.FindPrecessor(NextAVLNode);
end;
if NearestAVLNode<>nil then begin
// there is a NearestAVLNode in front -> insert behind body
ANodeExt2:=TCodeTreeNodeExtension(NearestAVLNode.Data);
// see above (note 1) for ANodeExt2.Data
ANode:=TCodeTreeNodeExtension(ANodeExt2.Data).Node;
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(ANode.EndPos);
end else if NextAVLNode<>nil then begin
// there is a NextAVLNode behind -> insert in front of body
ANodeExt2:=TCodeTreeNodeExtension(NextAVLNode.Data);
// see above (note 1) for ANodeExt2.Data
ANode:=TCodeTreeNodeExtension(ANodeExt2.Data).Node;
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
end;
end;
end;
CreateCodeForMissingProcBody(ANodeExt,Indent);
InsertProcBody(ANodeExt,InsertPos,0);
end;
end;
end;
Result:=true;
finally
DisposeAVLTree(ClassProcs);
DisposeAVLTree(ProcBodyNodes);
end;
end;
function TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc(
CleanPos: integer; OldTopLine: integer; AddMissingProcBodies: boolean; out
NewPos: TCodeXYPosition; out NewTopLine, BlockTopLine,
BlockBottomLine: integer): boolean;
var
OldCodeXYPos: TCodeXYPosition;
OldCodePos: TCodePosition;
CursorNode: TCodeTreeNode;
CurClassName: String;
ProcNode: TCodeTreeNode;
begin
Result:=false;
try
// extend class declaration
if not InsertAllNewClassParts then
RaiseException(20170421201817,ctsErrorDuringInsertingNewClassParts);
// create missing method bodies
if AddMissingProcBodies and (not CreateMissingClassProcBodies(true)) then
RaiseException(20170421201819,ctsErrorDuringCreationOfNewProcBodies);
CurClassName:=ExtractClassName(CodeCompleteClassNode,false);
// apply the changes and jump to first new proc body
if not CleanPosToCodePos(CleanPos,OldCodePos) then
RaiseException(20170421201822,'TCodeCompletionCodeTool.CompleteCode Internal Error CleanPosToCodePos');
if not CleanPosToCaret(CleanPos,OldCodeXYPos) then
RaiseException(20170421201826,'TCodeCompletionCodeTool.CompleteCode Internal Error CleanPosToCaret');
if not FSourceChangeCache.Apply then
RaiseException(20170421201828,ctsUnableToApplyChanges);
finally
FreeClassInsertionList;
end;
if FJumpToProcHead.Name<>'' then begin
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Jump to new proc body ... "',FJumpToProcHead.Name,'"');
{$ENDIF}
// there was a new proc body
// -> find it and jump to
// reparse code
BuildTreeAndGetCleanPos(OldCodeXYPos,CleanPos);
// find CodeTreeNode at cursor
CursorNode:=FindDeepestNodeAtPos(CleanPos,true);
// due to insertions in front of the class, the cursor position could
// have changed
if CursorNode<>nil then
CursorNode:=CursorNode.GetTopMostNodeOfType(ctnTypeSection);
FCodeCompleteClassNode:=FindClassNode(CursorNode,CurClassName,true,false);
if CodeCompleteClassNode=nil then
RaiseException(20170421201833,'oops, I lost your class');
ProcNode:=FindProcNode(CursorNode,FJumpToProcHead,[phpInUpperCase,phpIgnoreForwards]);
if ProcNode=nil then begin
debugln(['TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Proc="',FJumpToProcHead.Name,'"']);
RaiseException(20170421201835,ctsNewProcBodyNotFound);
end;
Result:=FindJumpPointInProcNode(ProcNode,NewPos,NewTopLine, BlockTopLine, BlockBottomLine);
end else begin
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.ApplyChangesAndJumpToFirstNewProc Adjust Cursor ... ');
{$ENDIF}
// there was no new proc body
// -> adjust cursor
AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine);
Result:=true;
end;
end;
function TCodeCompletionCodeTool.CompleteCode(CursorPos: TCodeXYPosition;
OldTopLine: integer; out NewPos: TCodeXYPosition; out NewTopLine,
BlockTopLine, BlockBottomLine: integer;
SourceChangeCache: TSourceChangeCache; Interactive: Boolean): boolean;
function TryCompleteLocalVar(CleanCursorPos: integer;
CursorNode: TCodeTreeNode): Boolean;
begin
// test if Local variable assignment (i:=3)
Result:=CompleteVariableAssignment(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,SourceChangeCache,Interactive);
if Result then exit;
// test if Local variable iterator (for i in j)
Result:=CompleteVariableForIn(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,SourceChangeCache, Interactive);
if Result then exit;
// test if undeclared local variable as parameter (GetPenPos(x,y))
Result:=CompleteIdentifierByParameter(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,SourceChangeCache,Interactive);
if Result then exit;
end;
function TryComplete(CursorNode: TCodeTreeNode; CleanCursorPos: integer): Boolean;
var
ProcNode, AClassNode: TCodeTreeNode;
IsEventAssignment: boolean;
begin
Result := False;
FCompletingCursorNode:=CursorNode;
try
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode A CleanCursorPos=',dbgs(CleanCursorPos),' NodeDesc=',NodeDescriptionAsString(CursorNode.Desc));
{$ENDIF}
// test if in a class
AClassNode:=FindClassOrInterfaceNode(CursorNode);
if AClassNode<>nil then begin
Result:=CompleteClass(AClassNode,CleanCursorPos,OldTopLine,CursorNode,
NewPos,NewTopLine, BlockTopLine, BlockBottomLine);
exit;
end;
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode not in-a-class ... ');
{$ENDIF}
// test if forward proc
//debugln('TCodeCompletionCodeTool.CompleteCode ',CursorNode.DescAsString);
if CursorNode.Desc = ctnInterface then
begin
//Search nearest (to the left) CursorNode if we are within interface section
CursorNode := CursorNode.LastChild;
while Assigned(CursorNode) and (CursorNode.StartPos > CleanCursorPos) do
CursorNode := CursorNode.PriorBrother;
if (CursorNode=nil)
or (not PositionsInSameLine(Src,CursorNode.EndPos,CleanCursorPos)) then
CursorNode:=FCompletingCursorNode;
end;
ProcNode:=CursorNode.GetNodeOfType(ctnProcedure);
if (ProcNode=nil) and (CursorNode.Desc=ctnProcedure) then
ProcNode:=CursorNode;
if (ProcNode<>nil) and (ProcNode.Desc=ctnProcedure)
and ((ProcNode.SubDesc and ctnsForwardDeclaration)>0) then begin
// Node is forward Proc
Result:=CompleteForwardProcs(CursorPos,ProcNode,CursorNode,NewPos,NewTopLine,
BlockTopLine, BlockBottomLine, SourceChangeCache);
exit;
end;
// test if Event assignment (MyClick:=@Button1.OnClick)
Result:=CompleteEventAssignment(CleanCursorPos,OldTopLine,CursorNode,
IsEventAssignment,NewPos,NewTopLine,SourceChangeCache,Interactive);
if IsEventAssignment then exit;
Result:=TryCompleteLocalVar(CleanCursorPos,CursorNode);
if Result then exit;
// test if procedure call
Result:=CompleteProcByCall(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,BlockTopLine,BlockBottomLine,SourceChangeCache);
if Result then exit;
finally
FCompletingCursorNode:=nil;
end;
end;
function TryFirstLocalIdentOccurence(CursorNode: TCodeTreeNode;
OrigCleanCursorPos, CleanCursorPos: Integer): boolean;
var
AtomContextNode, StatementNode: TCodeTreeNode;
IdentAtom, LastCurPos: TAtomPosition;
UpIdentifier: string;
LastAtomIsDot: Boolean;
Params: TFindDeclarationParams;
OldCodePos: TCodePosition;
begin
Result := false;
// get enclosing Begin block
if not (CursorNode.Desc in AllPascalStatements) then exit;
StatementNode:=CursorNode;
while StatementNode<>nil do begin
if (StatementNode.Desc=ctnBeginBlock) then begin
if (StatementNode.Parent.Desc in [ctnProcedure,ctnProgram]) then break;
end else if StatementNode.Desc in [ctnInitialization,ctnFinalization] then
break;
StatementNode:=StatementNode.Parent;
end;
if StatementNode=nil then exit;
// read UpIdentifier at CleanCursorPos
GetIdentStartEndAtPosition(Src,CleanCursorPos,
IdentAtom.StartPos,IdentAtom.EndPos);
if IdentAtom.StartPos=IdentAtom.EndPos then
Exit;
MoveCursorToAtomPos(IdentAtom);
if not AtomIsIdentifier then
Exit; // a keyword
UpIdentifier := GetUpAtom;
//find first occurence of UpIdentifier from procedure begin until CleanCursorPos
//we are interested only in local variables/identifiers
// --> the UpIdentifier must not be preceded by a point ("MyObject.I" - if we want to complete I)
// and then do another check if it is not available with the "with" command, e.g.
MoveCursorToCleanPos(StatementNode.StartPos);
if StatementNode.Desc=ctnBeginBlock then
BuildSubTreeForBeginBlock(StatementNode);
LastAtomIsDot := False;
while CurPos.EndPos < CleanCursorPos do
begin
ReadNextAtom;
if not LastAtomIsDot and AtomIsIdentifier and UpAtomIs(UpIdentifier) then
begin
AtomContextNode:=FindDeepestNodeAtPos(StatementNode,CurPos.StartPos,true);
Params:=TFindDeclarationParams.Create(Self, AtomContextNode);
try
// check if UpIdentifier doesn't exists (e.g. because of a with statement)
LastCurPos := CurPos;
if not IdentifierIsDefined(CurPos,AtomContextNode,Params) then
begin
FCompletingCursorNode:=CursorNode;
try
if not CleanPosToCodePos(OrigCleanCursorPos,OldCodePos) then
RaiseException(20170421201838,'TCodeCompletionCodeTool.TryFirstLocalIdentOccurence CleanPosToCodePos');
CompleteCode:=TryCompleteLocalVar(LastCurPos.StartPos,AtomContextNode);
AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine);
exit(true);
finally
FCompletingCursorNode:=nil;
end;
end;
CurPos := LastCurPos;//IdentifierIsDefined changes the CurPos
finally
Params.Free;
end;
end;
LastAtomIsDot := CurPos.Flag=cafPoint;
end;
end;
procedure ClearAndRaise(var E: ECodeToolError; CleanPos: Integer);
var
TempE: ECodeToolError;
begin
TempE := E;
E := nil;
MoveCursorToCleanPos(CleanPos);
RaiseExceptionInstance(TempE);
end;
function TryAssignment(CursorNode: TCodeTreeNode;
OrigCleanCursorPos, CleanCursorPos: Integer): Boolean;
var
OldCodePos: TCodePosition;
begin
// Search only within the current statement - stop on semicolon or keywords
// (else isn't prepended by a semicolon in contrast to other keywords).
Result := False;
MoveCursorToNearestAtom(CleanCursorPos);
while CurPos.StartPos > 1 do
begin
ReadPriorAtom;
case CurPos.Flag of
cafAssignment:
begin
// OK FOUND!
ReadPriorAtom;
FCompletingCursorNode:=CursorNode;
try
if TryComplete(CursorNode, CurPos.StartPos) then
begin
if not CleanPosToCodePos(OrigCleanCursorPos,OldCodePos) then
RaiseException(20170421201842,'TCodeCompletionCodeTool.CompleteCode CleanPosToCodePos');
AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine);
exit(true);
end;
break;
finally
FCompletingCursorNode:=nil;
end;
end;
cafWord: // stop on keywords
if UpAtomIs('BEGIN') or UpAtomIs('END')
or UpAtomIs('TRY') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('FOR') or UpAtomIs('TO') or UpAtomIs('DO')
or UpAtomIs('REPEAT') or UpAtomIs('UNTIL') or UpAtomIs('WHILE')
or UpAtomIs('IF') or UpAtomIs('THEN') or UpAtomIs('CASE') or UpAtomIs('ELSE')
then
break;
cafSemicolon:
break; // stop on semicolon
end;
end;
end;
var
CleanCursorPos, OrigCleanCursorPos: integer;
CursorNode: TCodeTreeNode;
LastCodeToolsErrorCleanPos: Integer;
LastCodeToolsError: ECodeToolError;
begin
BlockTopLine := -1;
BlockBottomLine := -1;
//DebugLn(['TCodeCompletionCodeTool.CompleteCode CursorPos=',Dbgs(CursorPos),' OldTopLine=',OldTopLine]);
Result:=false;
if (SourceChangeCache=nil) then
RaiseException(20170421201857,'need a SourceChangeCache');
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
OrigCleanCursorPos:=CleanCursorPos;
NewPos:=CleanCodeXYPosition;
NewTopLine:=0;
// find CodeTreeNode at cursor
// skip newline chars
while (CleanCursorPos>1) and (Src[CleanCursorPos] in [#10,#13]) do
dec(CleanCursorPos);
// skip space (first try left)
while (CleanCursorPos>1) and (Src[CleanCursorPos] in [' ',#9,';']) do
dec(CleanCursorPos);
if (CleanCursorPos>0) and (CleanCursorPos<SrcLen)
and (Src[CleanCursorPos] in [#10,#13]) then begin
// then try right
repeat
inc(CleanCursorPos);
until (CleanCursorPos>=SrcLen) or (not (Src[CleanCursorPos] in [' ',#9]));
end;
CodeCompleteSrcChgCache:=SourceChangeCache;
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
LastCodeToolsError := nil;
try
try
if TryComplete(CursorNode, CleanCursorPos) then
exit(true);
{ Find the first occurence of the (local) identifier at cursor in current
procedure body and try again. }
if TryFirstLocalIdentOccurence(CursorNode,OrigCleanCursorPos,CleanCursorPos) then
exit(true);
except
on E: ECodeToolError do
begin
// we have a codetool error, let's try to find the assignment in any case
LastCodeToolsErrorCleanPos := CurPos.StartPos;
LastCodeToolsError := ECodeToolError.Create(E.Sender,E.Id,E.Message);
end else
raise;
end;
// find first assignment before current.
if TryAssignment(CursorNode, OrigCleanCursorPos, CleanCursorPos) then
Exit(true);
if LastCodeToolsError<>nil then // no assignment found, reraise
ClearAndRaise(LastCodeToolsError, LastCodeToolsErrorCleanPos);
finally
LastCodeToolsError.Free;
end;
if CompleteMethodByBody(OrigCleanCursorPos,OldTopLine,CursorNode,
NewPos,NewTopLine,SourceChangeCache)
then
exit(true);
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CompleteCode nothing to complete ... ');
{$ENDIF}
end;
function TCodeCompletionCodeTool.CreateVariableForIdentifier(
CursorPos: TCodeXYPosition; OldTopLine: integer; out NewPos: TCodeXYPosition;
out NewTopLine: integer; SourceChangeCache: TSourceChangeCache; Interactive: Boolean
): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
begin
Result:=false;
NewPos:=CleanCodeXYPosition;
NewTopLine:=0;
if (SourceChangeCache=nil) then
RaiseException(20170421201910,'need a SourceChangeCache');
BuildTreeAndGetCleanPos(CursorPos, CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
CodeCompleteSrcChgCache:=SourceChangeCache;
{$IFDEF CTDEBUG}
DebugLn('TCodeCompletionCodeTool.CreateVariableForIdentifier A CleanCursorPos=',dbgs(CleanCursorPos),' NodeDesc=',NodeDescriptionAsString(CursorNode.Desc));
{$ENDIF}
// test if Local variable assignment (i:=3)
Result:=CompleteVariableAssignment(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,SourceChangeCache,Interactive);
if Result then exit;
// test if undeclared local variable as parameter (GetPenPos(x,y))
Result:=CompleteIdentifierByParameter(CleanCursorPos,OldTopLine,
CursorNode,NewPos,NewTopLine,SourceChangeCache,Interactive);
if Result then exit;
MoveCursorToCleanPos(CleanCursorPos);
RaiseException(20170421201915,'this syntax is not supported by variable completion');
end;
function TCodeCompletionCodeTool.AddMethods(CursorPos: TCodeXYPosition;
OldTopLine: integer; ListOfPCodeXYPosition: TFPList;
const VirtualToOverride: boolean; out NewPos: TCodeXYPosition; out
NewTopLine, BlockTopLine, BlockBottomLine: integer;
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;
VisibilityDesc: TCodeTreeNodeDesc;
NodeExt: TCodeTreeNodeExtension;
AVLNode: TAVLTreeNode;
ProcName: String;
NewClassPart: TNewClassPart;
Beauty: TBeautifyCodeOptions;
ProcCode: String;
CurClassName: String;
begin
Result:=false;
if (ListOfPCodeXYPosition=nil) or (ListOfPCodeXYPosition.Count=0) then
exit(true);
if (SourceChangeCache=nil) then
RaiseException(20170421201918,'need a SourceChangeCache');
CodeCompleteSrcChgCache:=SourceChangeCache;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
NewMethods:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
ActivateGlobalWriteLock;
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(CodeXYPos,CleanCursorPos);
// find node at position
ProcNode:=NewCodeTool.BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
if (ProcNode.Desc<>ctnProcedure)
or (ProcNode.Parent=nil) then begin
NewCodeTool.MoveCursorToNodeStart(ProcNode);
RaiseException(20170421201921,'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);
debugln(['TCodeCompletionCodeTool.AddMethods FullProcCode="',FullProcCode,'" VirtualStartPos=',VirtualStartPos]);
if VirtualStartPos>=1 then begin
// replace virtual with override
FullProcCode:=copy(FullProcCode,1,VirtualStartPos-1)
+'override;'
+copy(FullProcCode,VirtualEndPos,length(FullProcCode));
end;
// remove abstract
FullProcCode:=RemoveProcSpecifier(FullProcCode,'abstract',
NewCodeTool.Scanner.NestedComments);
end;
ProcCode:=NewCodeTool.ExtractProcHead(ProcNode,[phpWithStart,
phpWithoutClassName,phpWithVarModifiers,phpWithParameterNames,
phpWithResultType,phpWithCallingSpecs]);
ProcCode:=ProcCode+Beauty.LineEnd
+'begin'+Beauty.LineEnd
+Beauty.GetIndentStr(Beauty.Indent)+Beauty.LineEnd
+'end;';
// add method data
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Txt:=CleanProcCode;
NodeExt.ExtTxt1:=FullProcCode;
NodeExt.ExtTxt2:=ProcName;
NodeExt.ExtTxt3:=ProcCode;
NodeExt.Flags:=VisibilityDesc;
NewMethods.Add(NodeExt);
//DebugLn(['TCodeCompletionCodeTool.AddMethods ',i,' CleanProcTxt=',CleanProcCode,' FullProcTxt=',FullProcCode]);
end;
finally
DeactivateGlobalWriteLock;
end;
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
// find node at position
CursorNode:=FindDeepestNodeAtPos(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
else
CursorNode:=FindClassOrInterfaceNode(CursorNode);
if (CursorNode=nil) or (not (CursorNode.Desc in AllClasses)) then begin
DebugLn(['TIdentCompletionTool.AddMethods cursor not in a class']);
exit;
end;
//DebugLn(['TCodeCompletionCodeTool.AddMethods CursorNode=',CursorNode.DescAsString]);
CodeCompleteSrcChgCache:=SourceChangeCache;
CodeCompleteClassNode:=CursorNode;
CurClassName:=ExtractClassName(CursorNode,false);
// add methods
AVLNode:=NewMethods.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
CleanProcCode:=NodeExt.Txt;
FullProcCode:=NodeExt.ExtTxt1;
ProcName:=NodeExt.ExtTxt2;
ProcCode:=NodeExt.ExtTxt3;
VisibilityDesc:=TCodeTreeNodeDesc(NodeExt.Flags);
case VisibilityDesc of
ctnClassPrivate: NewClassPart:=ncpPrivateProcs;
ctnClassProtected: NewClassPart:=ncpProtectedProcs;
ctnClassPublic: NewClassPart:=ncpPublicProcs;
ctnClassPublished: NewClassPart:=ncpPublishedProcs;
else NewClassPart:=ncpPublicProcs;
end;
// change classname
ProcCode:=Beauty.AddClassAndNameToProc(ProcCode,CurClassName,ProcName);
AddClassInsertion(CleanProcCode,FullProcCode,ProcName,NewClassPart,nil,
ProcCode);
AVLNode:=NewMethods.FindSuccessor(AVLNode);
end;
// apply changes
if not ApplyChangesAndJumpToFirstNewProc(CleanCursorPos,OldTopLine,true,
NewPos,NewTopLine, BlockTopLine, BlockBottomLine) then exit;
Result:=true;
finally
FreeClassInsertionList;
DisposeAVLTree(NewMethods);
end;
end;
constructor TCodeCompletionCodeTool.Create;
begin
inherited Create;
FSetPropertyVariablename:='AValue';
FSetPropertyVariableIsPrefix := false;
FSetPropertyVariableUseConst := false;
FCompleteProperties:=true;
FAddInheritedCodeToOverrideMethod:=true;
end;
end.