mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-05 16:32:40 +02:00
1981 lines
74 KiB
ObjectPascal
1981 lines
74 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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
|
|
|
|
ToDo:
|
|
-ProcExists: search procs in ancestors too
|
|
-VarExists: search vars in ancestors too
|
|
}
|
|
unit CodeCompletionTool;
|
|
|
|
{$ifdef FPC}{$mode objfpc}{$endif}{$H+}
|
|
|
|
interface
|
|
|
|
{$I codetools.inc}
|
|
|
|
{ $DEFINE CTDEBUG}
|
|
|
|
uses
|
|
{$IFDEF MEM_CHECK}
|
|
MemCheck,
|
|
{$ENDIF}
|
|
Classes, SysUtils, CodeToolsStrConsts, CodeTree, CodeAtom, PascalParserTool,
|
|
MethodJumpTool, FindDeclarationTool, SourceLog, KeywordFuncLists,
|
|
BasicCodeTools, LinkScanner, CodeCache, AVL_Tree, TypInfo, SourceChanger;
|
|
|
|
type
|
|
TNewClassPart = (ncpPrivateProcs, ncpPrivateVars,
|
|
ncpPublishedProcs, ncpPublishedVars);
|
|
|
|
TCodeCompletionCodeTool = class(TMethodJumpingCodeTool)
|
|
private
|
|
ASourceChangeCache: TSourceChangeCache;
|
|
ClassNode: TCodeTreeNode; // the class that is to be completed
|
|
StartNode: TCodeTreeNode; // the first variable/method/GUID node in ClassNode
|
|
FAddInheritedCodeToOverrideMethod: boolean;
|
|
FCompleteProperties: boolean;
|
|
FirstInsert: TCodeTreeNodeExtension; // list of insert requests
|
|
FSetPropertyVariablename: string;
|
|
JumpToProcName: string;
|
|
NewPrivatSectionIndent, NewPrivatSectionInsertPos: integer;
|
|
FullTopLvlName: string;
|
|
procedure AddNewPropertyAccessMethodsToClassProcs(ClassProcs: TAVLTree;
|
|
const TheClassName: string);
|
|
procedure CheckForOverrideAndAddInheritedCode(ClassProcs: TAVLTree);
|
|
function CompleteProperty(PropNode: TCodeTreeNode): boolean;
|
|
procedure SetCodeCompleteClassNode(const AClassNode: TCodeTreeNode);
|
|
procedure SetCodeCompleteSrcChgCache(const AValue: TSourceChangeCache);
|
|
function OnTopLvlIdentifierFound(Params: TFindDeclarationParams;
|
|
FoundContext: TFindContext): TIdentifierFoundResult;
|
|
protected
|
|
function ProcExistsInCodeCompleteClass(const NameAndParams: string): boolean;
|
|
function VarExistsInCodeCompleteClass(const UpperName: string): boolean;
|
|
procedure AddClassInsertion(PosNode: TCodeTreeNode;
|
|
const CleanDef, Def, IdentifierName, Body: string;
|
|
TheType: TNewClassPart);
|
|
procedure FreeClassInsertionList;
|
|
procedure InsertNewClassParts(PartType: TNewClassPart);
|
|
function InsertAllNewClassParts: boolean;
|
|
function CreateMissingProcBodies: boolean;
|
|
function NodeExtIsVariable(ANodeExt: TCodeTreeNodeExtension): boolean;
|
|
function NodeExtIsPrivate(ANodeExt: TCodeTreeNodeExtension): boolean;
|
|
property CodeCompleteClassNode: TCodeTreeNode
|
|
read ClassNode write SetCodeCompleteClassNode;
|
|
property CodeCompleteSrcChgCache: TSourceChangeCache
|
|
read ASourceChangeCache write SetCodeCompleteSrcChgCache;
|
|
public
|
|
function AddPublishedVariable(const UpperClassName,VarName, VarType: string;
|
|
SourceChangeCache: TSourceChangeCache): boolean; override;
|
|
function CompleteCode(CursorPos: TCodeXYPosition;
|
|
var NewPos: TCodeXYPosition; var NewTopLine: integer;
|
|
SourceChangeCache: TSourceChangeCache): boolean;
|
|
constructor Create;
|
|
property SetPropertyVariablename: string
|
|
read FSetPropertyVariablename write FSetPropertyVariablename;
|
|
property CompleteProperties: boolean
|
|
read FCompleteProperties write FCompleteProperties;
|
|
property AddInheritedCodeToOverrideMethod: boolean
|
|
read FAddInheritedCodeToOverrideMethod write FAddInheritedCodeToOverrideMethod;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
{ TCodeCompletionCodeTool }
|
|
|
|
function TCodeCompletionCodeTool.ProcExistsInCodeCompleteClass(
|
|
const NameAndParams: string): boolean;
|
|
// 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
|
|
var ANodeExt: TCodeTreeNodeExtension;
|
|
begin
|
|
Result:=false;
|
|
// search in new nodes, which will be inserted
|
|
ANodeExt:=FirstInsert;
|
|
while ANodeExt<>nil do begin
|
|
if CompareTextIgnoringSpace(ANodeExt.Txt,NameAndParams,true)=0 then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
ANodeExt:=ANodeExt.Next;
|
|
end;
|
|
if not Result then begin
|
|
// ToDo: check ancestor procs too
|
|
// search in current class
|
|
Result:=(FindProcNode(StartNode,NameAndParams,[phpInUpperCase])<>nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.SetCodeCompleteClassNode(
|
|
const AClassNode: TCodeTreeNode);
|
|
begin
|
|
FreeClassInsertionList;
|
|
ClassNode:=AClassNode;
|
|
BuildSubTreeForClass(ClassNode);
|
|
StartNode:=ClassNode.FirstChild;
|
|
while (StartNode<>nil) and (StartNode.FirstChild=nil) do
|
|
StartNode:=StartNode.NextBrother;
|
|
if StartNode<>nil then StartNode:=StartNode.FirstChild;
|
|
JumpToProcName:='';
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.SetCodeCompleteSrcChgCache(
|
|
const AValue: TSourceChangeCache);
|
|
begin
|
|
ASourceChangeCache:=AValue;
|
|
ASourceChangeCache.MainScanner:=Scanner;
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.OnTopLvlIdentifierFound(
|
|
Params: TFindDeclarationParams; FoundContext: TFindContext
|
|
): TIdentifierFoundResult;
|
|
var TrimmedIdentifier: string;
|
|
begin
|
|
if not (fdfTopLvlResolving in Params.Flags) then exit;
|
|
with Params do begin
|
|
case NewNode.Desc of
|
|
ctnTypeDefinition,ctnVarDefinition,ctnConstDefinition:
|
|
TrimmedIdentifier:=NewCodeTool.ExtractIdentifier(NewNode.StartPos);
|
|
ctnProperty:
|
|
begin
|
|
NewCodeTool.MoveCursorToNodeStart(NewNode);
|
|
NewCodeTool.ReadNextAtom; // 'property'
|
|
NewCodeTool.ReadNextAtom; // name
|
|
TrimmedIdentifier:=NewCodeTool.GetAtom;
|
|
end;
|
|
else
|
|
TrimmedIdentifier:=GetIdentifier(Params.Identifier);
|
|
end;
|
|
end;
|
|
FullTopLvlName:=FullTopLvlName+TrimmedIdentifier;
|
|
Result:=ifrSuccess;
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.VarExistsInCodeCompleteClass(
|
|
const UpperName: string): boolean;
|
|
var ANodeExt: TCodeTreeNodeExtension;
|
|
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 begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
ANodeExt:=ANodeExt.Next;
|
|
end;
|
|
if not Result then begin
|
|
// ToDo: check ancestor vars too
|
|
// search in current class
|
|
Result:=(FindVarNode(StartNode,UpperName)<>nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.AddClassInsertion(PosNode: TCodeTreeNode;
|
|
const CleanDef, Def, IdentifierName, Body: string; TheType: TNewClassPart);
|
|
{ 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
|
|
|
|
PosNode: The node, to which the request belongs. e.g. the property node, if
|
|
the insert is the auto created private variable
|
|
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
|
|
Body: optional. Normally a method body is auto created. This overrides
|
|
the body code.
|
|
TheType: see TNewClassPart
|
|
|
|
}
|
|
var NewInsert, InsertPos, LastInsertPos: TCodeTreeNodeExtension;
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.AddClassInsertion] ',CleanDef,',',Def,',',Identifiername);
|
|
{$ENDIF}
|
|
NewInsert:=NodeExtMemManager.NewNode;
|
|
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;
|
|
if ASourceChangeCache.BeautifyCodeOptions.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;
|
|
//writeln('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
|
|
writeln(' HHH ',InsertPos.Txt);
|
|
InsertPos:=InsertPos.Next;
|
|
end;}
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.FreeClassInsertionList;
|
|
// dispose all new variables/procs definitions
|
|
var ANodeExt: TCodeTreeNodeExtension;
|
|
begin
|
|
while FirstInsert<>nil do begin
|
|
ANodeExt:=FirstInsert;
|
|
FirstInsert:=FirstInsert.Next;
|
|
NodeExtMemManager.DisposeNode(ANodeExt);
|
|
end;
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.NodeExtIsVariable(
|
|
ANodeExt: TCodeTreeNodeExtension): boolean;
|
|
begin
|
|
Result:=(ANodeExt.Flags=ord(ncpPrivateVars))
|
|
or (ANodeExt.Flags=ord(ncpPublishedVars));
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.NodeExtIsPrivate(
|
|
ANodeExt: TCodeTreeNodeExtension): boolean;
|
|
begin
|
|
Result:=(ANodeExt.Flags=ord(ncpPrivateVars))
|
|
or (ANodeExt.Flags=ord(ncpPrivateProcs));
|
|
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(false);
|
|
if not EndOfSourceFound then exit;
|
|
ClassNode:=FindClassNodeInInterface(UpperClassName,true,false);
|
|
// initialize class for code completion
|
|
CodeCompleteClassNode:=ClassNode;
|
|
CodeCompleteSrcChgCache:=SourceChangeCache;
|
|
// check if variable already exists
|
|
if VarExistsInCodeCompleteClass(UpperCaseStr(VarName)) then begin
|
|
|
|
end else begin
|
|
AddClassInsertion(nil,UpperCaseStr(VarName),
|
|
VarName+':'+VarType+';',VarName,'',ncpPublishedVars);
|
|
if not InsertAllNewClassParts then
|
|
RaiseException(ctsErrorDuringInsertingNewClassParts);
|
|
// apply the changes
|
|
if not SourceChangeCache.Apply then
|
|
RaiseException(ctsUnableToApplyChanges);
|
|
end;
|
|
Result:=true;
|
|
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 Col8: ICol8 read FCol8 write FCol8 implements ICol8;
|
|
|
|
property specifiers without parameters:
|
|
;nodefault, ;default
|
|
|
|
property specifiers with parameters:
|
|
index <constant>, read <id>, write <id>, implements <id>,
|
|
stored <id>, default <constant>
|
|
}
|
|
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'
|
|
);
|
|
|
|
var Parts: array[TPropPart] of TAtomPosition;
|
|
|
|
procedure ReadSimpleSpec(SpecWord, SpecParam: TPropPart);
|
|
begin
|
|
if Parts[SpecWord].StartPos>=1 then
|
|
RaiseExceptionFmt(ctsPropertySpecifierAlreadyDefined,[GetAtom]);
|
|
Parts[SpecWord]:=CurPos;
|
|
ReadNextAtom;
|
|
if AtomIsChar(';') then exit;
|
|
AtomIsIdentifier(true);
|
|
if WordIsPropertySpecifier.DoItUpperCase(UpperSrc,CurPos.StartPos,
|
|
CurPos.EndPos-CurPos.StartPos) then exit;
|
|
Parts[SpecParam]:=CurPos;
|
|
ReadNextAtom;
|
|
end;
|
|
|
|
var AccessParam, AccessParamPrefix, CleanAccessFunc, AccessFunc,
|
|
CleanParamList, ParamList, PropType, ProcBody, VariableName: string;
|
|
InsertPos: integer;
|
|
BeautifyCodeOpts: TBeautifyCodeOptions;
|
|
|
|
procedure InitCompleteProperty;
|
|
var APart: TPropPart;
|
|
begin
|
|
for APart:=Low(TPropPart) to High(TPropPart) do
|
|
Parts[APart].StartPos:=-1;
|
|
end;
|
|
|
|
procedure ReadPropertyKeywordAndName;
|
|
begin
|
|
MoveCursorToNodeStart(PropNode);
|
|
ReadNextAtom; // read 'property'
|
|
ReadNextAtom; // read name
|
|
Parts[ppName]:=CurPos;
|
|
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}
|
|
writeln('[TCodeCompletionCodeTool.CompleteProperty] error parsing param list');
|
|
{$ENDIF}
|
|
RaiseException(ctsErrorInParamList);
|
|
end;
|
|
CleanParamList:=GetExtraction;
|
|
Parts[ppParamList].EndPos:=CurPos.EndPos;
|
|
end else
|
|
CleanParamList:='';
|
|
end;
|
|
|
|
procedure ReadPropertyType;
|
|
begin
|
|
ReadNextAtom; // read type
|
|
if (CurPos.StartPos>PropNode.EndPos)
|
|
or UpAtomIs('END') or AtomIsChar(';') or (not AtomIsIdentifier(false))
|
|
or AtomIsKeyWord then begin
|
|
// no type name found -> ignore this property
|
|
RaiseExceptionFmt(ctsPropertTypeExpectedButAtomFound,[GetAtom]);
|
|
end;
|
|
Parts[ppType]:=CurPos;
|
|
ReadNextAtom;
|
|
end;
|
|
|
|
procedure ReadIndexSpecifier;
|
|
begin
|
|
if UpAtomIs('INDEX') then begin
|
|
if Parts[ppIndexWord].StartPos>=1 then
|
|
RaiseException(ctsIndexSpecifierRedefined);
|
|
Parts[ppIndexWord]:=CurPos;
|
|
ReadNextAtom;
|
|
if WordIsPropertySpecifier.DoItUpperCase(UpperSrc,CurPos.StartPos,
|
|
CurPos.EndPos-CurPos.StartPos) then
|
|
RaiseExceptionFmt(ctsIndexParameterExpectedButAtomFound,[GetAtom]);
|
|
Parts[ppIndex].StartPos:=CurPos.StartPos;
|
|
ReadConstant(true,false,[]);
|
|
Parts[ppIndex].EndPos:=LastAtoms.GetValueAt(0).EndPos;
|
|
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) and (not AtomIsChar(';'))
|
|
and (not UpAtomIs('END')) do begin
|
|
if UpAtomIs('STORED') then begin
|
|
ReadSimpleSpec(ppStoredWord,ppStored);
|
|
end else if UpAtomIs('DEFAULT') then begin
|
|
if Parts[ppDefaultWord].StartPos>=1 then
|
|
RaiseException(ctsDefaultSpecifierRedefined);
|
|
Parts[ppDefaultWord]:=CurPos;
|
|
ReadNextAtom;
|
|
if WordIsPropertySpecifier.DoItUpperCase(UpperSrc,CurPos.StartPos,
|
|
CurPos.EndPos-CurPos.StartPos) then
|
|
RaiseExceptionFmt(ctsDefaultParameterExpectedButAtomFound,[GetAtom]);
|
|
Parts[ppDefault].StartPos:=CurPos.StartPos;
|
|
ReadConstant(true,false,[]);
|
|
Parts[ppDefault].EndPos:=LastAtoms.GetValueAt(0).EndPos;
|
|
end else if UpAtomIs('IMPLEMENTS') then begin
|
|
ReadSimpleSpec(ppImplementsWord,ppImplements);
|
|
end else if UpAtomIs('NODEFAULT') then begin
|
|
if Parts[ppNoDefaultWord].StartPos>=1 then
|
|
RaiseException(ctsNodefaultSpecifierDefinedTwice);
|
|
Parts[ppNoDefaultWord]:=CurPos;
|
|
ReadNextAtom;
|
|
end else
|
|
RaiseExceptionFmt(ctsStrExpectedButAtomFound,[';',GetAtom]);
|
|
end;
|
|
if (CurPos.StartPos>PropNode.EndPos) then
|
|
RaiseException('Reparsing error (Complete Property)');
|
|
end;
|
|
|
|
procedure CompleteReadSpecifier;
|
|
begin
|
|
// check read specifier
|
|
VariableName:='';
|
|
if (Parts[ppReadWord].StartPos>0) or (Parts[ppWriteWord].StartPos<1) then
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[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
|
|
AccessParam:='';
|
|
if (Parts[ppParamList].StartPos>0) or (Parts[ppIndexWord].StartPos>0)
|
|
or (AnsiCompareText(AccessParamPrefix,
|
|
LeftStr(AccessParam,length(AccessParamPrefix)))=0) then
|
|
begin
|
|
// the read identifier is a function
|
|
if Parts[ppRead].StartPos<1 then
|
|
AccessParam:=AccessParamPrefix+copy(Src,Parts[ppName].StartPos,
|
|
Parts[ppName].EndPos-Parts[ppName].StartPos);
|
|
if (Parts[ppParamList].StartPos>0) then begin
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// param list, no index
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'('+CleanParamList+');';
|
|
end else begin
|
|
// index + param list
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'(:INTEGER;'
|
|
+CleanParamList+');';
|
|
end;
|
|
end else begin
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// no param list, no index
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+';';
|
|
end else begin
|
|
// index, no param list
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'(:INTEGER);';
|
|
end;
|
|
end;
|
|
// check if function exists
|
|
if not ProcExistsInCodeCompleteClass(CleanAccessFunc) then begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[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}
|
|
writeln('[TCodeCompletionCodeTool.CompleteProperty] Error reading param list');
|
|
{$ENDIF}
|
|
RaiseException(ctsErrorInParamList);
|
|
end;
|
|
ParamList:=GetExtraction;
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// param list, no index
|
|
AccessFunc:='function '+AccessParam
|
|
+'('+ParamList+'):'+PropType+';';
|
|
end else begin
|
|
// index + param list
|
|
AccessFunc:='function '+AccessParam
|
|
+'(Index:integer;'+ParamList+'):'+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
|
|
+'(Index:integer):'+PropType+';';
|
|
end;
|
|
end;
|
|
// add new Insert Node
|
|
if CompleteProperties then
|
|
AddClassInsertion(PropNode,CleanAccessFunc,AccessFunc,AccessParam,
|
|
'',ncpPrivateProcs);
|
|
end;
|
|
end else begin
|
|
// the read identifier is a variable
|
|
if Parts[ppRead].StartPos<1 then
|
|
AccessParam:=BeautifyCodeOpts.PrivatVariablePrefix
|
|
+copy(Src,Parts[ppName].StartPos,
|
|
Parts[ppName].EndPos-Parts[ppName].StartPos);
|
|
VariableName:=AccessParam;
|
|
if not VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)) then
|
|
begin
|
|
// variable does not exist yet -> add insert demand for variable
|
|
if CompleteProperties then
|
|
AddClassInsertion(PropNode,UpperCaseStr(AccessParam),
|
|
AccessParam+':'+PropType+';',AccessParam,'',ncpPrivateVars);
|
|
end;
|
|
end;
|
|
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;
|
|
ASourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
|
|
AccessParam);
|
|
end else begin
|
|
// 'read' keyword does not exist -> insert behind index and type
|
|
if Parts[ppIndexWord].StartPos>0 then
|
|
InsertPos:=Parts[ppIndexWord].EndPos
|
|
else if Parts[ppIndex].StartPos>0 then
|
|
InsertPos:=Parts[ppIndex].EndPos
|
|
else
|
|
InsertPos:=Parts[ppType].EndPos;
|
|
ASourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
|
|
BeautifyCodeOpts.BeautifyKeyWord('read')+' '+AccessParam);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure CompleteWriteSpecifier;
|
|
begin
|
|
// check write specifier
|
|
if (Parts[ppWriteWord].StartPos>0) or (Parts[ppReadWord].StartPos<1) then
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[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+copy(Src,Parts[ppName].StartPos,
|
|
Parts[ppName].EndPos-Parts[ppName].StartPos);
|
|
if (Parts[ppParamList].StartPos>0) or (Parts[ppIndexWord].StartPos>0)
|
|
or (AnsiCompareText(AccessParamPrefix,
|
|
LeftStr(AccessParam,length(AccessParamPrefix)))=0) then
|
|
begin
|
|
// the write identifier is a procedure
|
|
if (Parts[ppParamList].StartPos>0) then begin
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// param list, no index
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'('+CleanParamList+';'
|
|
+' :'+UpperCaseStr(PropType)+');';
|
|
end else begin
|
|
// index + param list
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'(:INTEGER;'
|
|
+CleanParamList+'; :'+UpperCaseStr(PropType)+');';
|
|
end;
|
|
end else begin
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// no param list, no index
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)
|
|
+'( :'+UpperCaseStr(PropType)+');';
|
|
end else begin
|
|
// index, no param list
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam)+'(:INTEGER;'
|
|
+' :'+UpperCaseStr(PropType)+');';
|
|
end;
|
|
end;
|
|
// check if procedure exists
|
|
if not ProcExistsInCodeCompleteClass(CleanAccessFunc) then begin
|
|
// add insert demand for function
|
|
// build function code
|
|
ProcBody:='';
|
|
if (Parts[ppParamList].StartPos>0) then begin
|
|
MoveCursorToCleanPos(Parts[ppParamList].StartPos);
|
|
ReadNextAtom;
|
|
InitExtraction;
|
|
if not ReadParamList(true,true,[phpWithParameterNames,
|
|
phpWithoutBrackets,phpWithVarModifiers,
|
|
phpWithComments])
|
|
then
|
|
RaiseException(ctsErrorInParamList);
|
|
ParamList:=GetExtraction;
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// param list, no index
|
|
AccessFunc:='procedure '+AccessParam
|
|
+'('+ParamList+';const '+SetPropertyVariablename+': '
|
|
+PropType+');';
|
|
end else begin
|
|
// index + param list
|
|
AccessFunc:='procedure '+AccessParam
|
|
+'(Index:integer;'+ParamList+';'
|
|
+'const '+SetPropertyVariablename+': '+PropType+');';
|
|
end;
|
|
end else begin
|
|
if (Parts[ppIndexWord].StartPos<1) then begin
|
|
// no param list, no index
|
|
AccessFunc:=
|
|
'procedure '+AccessParam
|
|
+'(const '+SetPropertyVariablename+': '+PropType+');';
|
|
if VariableName<>'' then begin
|
|
// read spec is a variable -> add simple assign code to body
|
|
ProcBody:=
|
|
'procedure '
|
|
+ExtractClassName(PropNode.Parent.Parent,false)+'.'+AccessParam
|
|
+'(const '+SetPropertyVariablename+': '+PropType+');'
|
|
+BeautifyCodeOpts.LineEnd
|
|
+'begin'+BeautifyCodeOpts.LineEnd
|
|
+GetIndentStr(BeautifyCodeOpts.Indent)+
|
|
+VariableName+':='+SetPropertyVariablename+';'
|
|
+BeautifyCodeOpts.LineEnd
|
|
+'end;';
|
|
end;
|
|
end else begin
|
|
// index, no param list
|
|
AccessFunc:='procedure '+AccessParam
|
|
+'(Index:integer; const '+SetPropertyVariablename+': '
|
|
+PropType+');';
|
|
end;
|
|
end;
|
|
// add new Insert Node
|
|
if CompleteProperties then
|
|
AddClassInsertion(PropNode,CleanAccessFunc,AccessFunc,AccessParam,
|
|
ProcBody,ncpPrivateProcs);
|
|
end;
|
|
end else begin
|
|
// the write identifier is a variable
|
|
if not VarExistsInCodeCompleteClass(UpperCaseStr(AccessParam)) then
|
|
begin
|
|
// variable does not exist yet -> add insert demand for variable
|
|
if CompleteProperties then
|
|
AddClassInsertion(PropNode,UpperCaseStr(AccessParam),
|
|
AccessParam+':'+PropType+';',AccessParam,'',ncpPrivateVars);
|
|
end;
|
|
end;
|
|
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;
|
|
ASourceChangeCache.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[ppIndexWord].StartPos>0 then
|
|
InsertPos:=Parts[ppIndexWord].EndPos
|
|
else if Parts[ppIndex].StartPos>0 then
|
|
InsertPos:=Parts[ppIndex].EndPos
|
|
else
|
|
InsertPos:=Parts[ppType].EndPos;
|
|
ASourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
|
|
BeautifyCodeOpts.BeautifyKeyWord('write')+' '+AccessParam);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure CompleteStoredSpecifier;
|
|
begin
|
|
// check stored specifier
|
|
if (Parts[ppStoredWord].StartPos>0) then begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.CompleteProperty] stored specifier needed');
|
|
{$ENDIF}
|
|
if Parts[ppStored].StartPos>0 then
|
|
AccessParam:=copy(Src,Parts[ppStored].StartPos,
|
|
Parts[ppStored].EndPos-Parts[ppStored].StartPos)
|
|
else
|
|
AccessParam:=copy(Src,Parts[ppName].StartPos,
|
|
Parts[ppName].EndPos-Parts[ppName].StartPos)
|
|
+BeautifyCodeOpts.PropertyStoredIdentPostfix;
|
|
CleanAccessFunc:=UpperCaseStr(AccessParam);
|
|
// check if procedure exists
|
|
if (not ProcExistsInCodeCompleteClass(CleanAccessFunc+';'))
|
|
and (not VarExistsInCodeCompleteClass(CleanAccessFunc))
|
|
then begin
|
|
// add insert demand for function
|
|
// build function code
|
|
AccessFunc:='function '+AccessParam+':boolean;';
|
|
CleanAccessFunc:=CleanAccessFunc+';';
|
|
// add new Insert Node
|
|
if CompleteProperties then
|
|
AddClassInsertion(PropNode,CleanAccessFunc,AccessFunc,AccessParam,'',
|
|
ncpPrivateProcs);
|
|
end;
|
|
if Parts[ppStored].StartPos<0 then begin
|
|
// insert stored specifier
|
|
InsertPos:=Parts[ppStoredWord].EndPos;
|
|
if CompleteProperties then
|
|
ASourceChangeCache.Replace(gtSpace,gtNone,InsertPos,InsertPos,
|
|
AccessParam);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result:=false;
|
|
InitCompleteProperty;
|
|
ReadPropertyKeywordAndName;
|
|
ReadPropertyParamList;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.CompleteProperty] Checking Property ',GetAtom);
|
|
{$ENDIF}
|
|
if not AtomIsChar(':') then begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.CompleteProperty] no type : found -> ignore property');
|
|
{$ENDIF}
|
|
// no type -> ignore this property
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
|
|
ReadPropertyType;
|
|
// parse specifiers
|
|
ReadIndexSpecifier;
|
|
ReadReadSpecifier;
|
|
ReadWriteSpecifier;
|
|
ReadOptionalSpecifiers;
|
|
PropType:=copy(Src,Parts[ppType].StartPos,
|
|
Parts[ppType].EndPos-Parts[ppType].StartPos);
|
|
|
|
// complete property
|
|
BeautifyCodeOpts:=ASourceChangeCache.BeautifyCodeOptions;
|
|
CompleteReadSpecifier;
|
|
CompleteWriteSpecifier;
|
|
CompleteStoredSpecifier;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.InsertNewClassParts(PartType: TNewClassPart);
|
|
var ANodeExt: TCodeTreeNodeExtension;
|
|
ClassSectionNode, ANode, InsertNode: TCodeTreeNode;
|
|
Indent, InsertPos: integer;
|
|
CurCode: string;
|
|
IsVariable: boolean;
|
|
begin
|
|
ANodeExt:=FirstInsert;
|
|
// insert all nodes of specific type
|
|
while ANodeExt<>nil do begin
|
|
IsVariable:=NodeExtIsVariable(ANodeExt);
|
|
if (ord(PartType)=ANodeExt.Flags) then begin
|
|
// search a destination section
|
|
if NodeExtIsPrivate(ANodeExt) then begin
|
|
// search a privat section in front of the node
|
|
ClassSectionNode:=ANodeExt.Node.Parent.PriorBrother;
|
|
while (ClassSectionNode<>nil)
|
|
and (ClassSectionNode.Desc<>ctnClassPrivate) do
|
|
ClassSectionNode:=ClassSectionNode.PriorBrother;
|
|
end else begin
|
|
// insert into first published section
|
|
ClassSectionNode:=ClassNode.FirstChild;
|
|
// 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;
|
|
if ClassSectionNode=nil then begin
|
|
// there is no existing class section node
|
|
// -> insert in the new one
|
|
Indent:=NewPrivatSectionIndent
|
|
+ASourceChangeCache.BeautifyCodeOptions.Indent;
|
|
InsertPos:=NewPrivatSectionInsertPos;
|
|
end else begin
|
|
// there is an existing class section to insert into
|
|
InsertNode:=nil; // the new part will be inserted after this node
|
|
// nil means insert as first
|
|
ANode:=ClassSectionNode.FirstChild;
|
|
if (ANode<>nil) and (ANode.Desc=ctnClassGUID) then
|
|
ANode:=ANode.NextBrother;
|
|
if not IsVariable then begin
|
|
// insert procs after variables
|
|
while (ANode<>nil) and (ANode.Desc=ctnVarDefinition) do begin
|
|
InsertNode:=ANode;
|
|
ANode:=ANode.NextBrother;
|
|
end;
|
|
end;
|
|
case ASourceChangeCache.BeautifyCodeOptions.ClassPartInsertPolicy of
|
|
cpipAlphabetically:
|
|
begin
|
|
while ANode<>nil do begin
|
|
if (IsVariable) then begin
|
|
if (ANode.Desc<>ctnVarDefinition)
|
|
or (CompareNodeIdentChars(ANode,ANodeExt.Txt)<0) then
|
|
break;
|
|
end else begin
|
|
case ANode.Desc of
|
|
ctnProcedure:
|
|
begin
|
|
CurCode:=ExtractProcName(ANode,[]);
|
|
if AnsiCompareStr(CurCode,ANodeExt.ExtTxt2)>0 then
|
|
break;
|
|
end;
|
|
ctnProperty:
|
|
begin
|
|
CurCode:=ExtractPropName(ANode,false);
|
|
if AnsiCompareStr(CurCode,ANodeExt.ExtTxt2)>0 then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
InsertNode:=ANode;
|
|
ANode:=ANode.NextBrother;
|
|
end;
|
|
end;
|
|
else
|
|
// cpipLast
|
|
begin
|
|
while ANode<>nil do begin
|
|
if (IsVariable) and (ANode.Desc<>ctnVarDefinition) then
|
|
break;
|
|
InsertNode:=ANode;
|
|
ANode:=ANode.NextBrother;
|
|
end;
|
|
end
|
|
end;
|
|
if InsertNode<>nil then begin
|
|
// insert after InsertNode
|
|
Indent:=GetLineIndent(Src,InsertNode.StartPos);
|
|
InsertPos:=FindFirstLineEndAfterInCode(InsertNode.EndPos);
|
|
end else begin
|
|
// insert as first variable/proc
|
|
Indent:=GetLineIndent(Src,ClassSectionNode.StartPos)
|
|
+ASourceChangeCache.BeautifyCodeOptions.Indent;
|
|
InsertPos:=FindFirstLineEndAfterInCode(ClassSectionNode.StartPos);
|
|
end;
|
|
end;
|
|
CurCode:=ANodeExt.ExtTxt1;
|
|
CurCode:=ASourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
|
|
CurCode,Indent);
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.InsertNewClassParts:');
|
|
writeln(CurCode);
|
|
{$ENDIF}
|
|
ASourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
|
|
CurCode);
|
|
if (not IsVariable)
|
|
and (ASourceChangeCache.BeautifyCodeOptions.MethodInsertPolicy
|
|
=mipClassOrder) then
|
|
begin
|
|
// this was a new method defnition 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 ANodeExt: TCodeTreeNodeExtension;
|
|
PrivatNode, ANode, TopMostPrivateNode: TCodeTreeNode;
|
|
PublishedNeeded: boolean;
|
|
begin
|
|
if FirstInsert=nil then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
NewPrivatSectionInsertPos:=-1;
|
|
NewPrivatSectionIndent:=0;
|
|
PublishedNeeded:=false;// 'published' keyword after first private section needed
|
|
PrivatNode:=nil;
|
|
// search topmost node of private node extensions
|
|
TopMostPrivateNode:=nil;
|
|
ANodeExt:=FirstInsert;
|
|
while ANodeExt<>nil do begin
|
|
if ((TopMostPrivateNode=nil)
|
|
or (TopMostPrivateNode.StartPos>ANodeExt.Node.StartPos))
|
|
and (NodeExtIsPrivate(ANodeExt))
|
|
then
|
|
TopMostPrivateNode:=ANodeExt.Node;
|
|
ANodeExt:=ANodeExt.Next;
|
|
end;
|
|
if TopMostPrivateNode<>nil then begin
|
|
// search privat section in front of topmost node
|
|
PrivatNode:=TopMostPrivateNode.Parent.PriorBrother;
|
|
while (PrivatNode<>nil) and (PrivatNode.Desc<>ctnClassPrivate) do
|
|
PrivatNode:=PrivatNode.PriorBrother;
|
|
if (PrivatNode=nil) then begin
|
|
{ Insert a new private section in front of topmost node
|
|
normally the best place for a new private section is at the end of
|
|
the first published section. But if a privat variable is already
|
|
needed in the first published section, then the new private section
|
|
must be inserted in front of all }
|
|
if (ClassNode.FirstChild.EndPos>TopMostPrivateNode.StartPos) then begin
|
|
// topmost node is in the first section
|
|
// -> insert as the first section
|
|
ANode:=ClassNode.FirstChild;
|
|
NewPrivatSectionIndent:=GetLineIndent(Src,ANode.StartPos);
|
|
if (ANode.FirstChild<>nil) and (ANode.FirstChild.Desc<>ctnClassGUID)
|
|
then
|
|
NewPrivatSectionInsertPos:=ANode.StartPos
|
|
else
|
|
NewPrivatSectionInsertPos:=ANode.FirstChild.EndPos;
|
|
PublishedNeeded:=CompareNodeIdentChars(ANode,'PUBLISHED')<>0;
|
|
end else begin
|
|
// default: insert new privat section behind first published section
|
|
ANode:=ClassNode.FirstChild;
|
|
NewPrivatSectionIndent:=GetLineIndent(Src,ANode.StartPos);
|
|
NewPrivatSectionInsertPos:=ANode.EndPos;
|
|
end;
|
|
ASourceChangeCache.Replace(gtNewLine,gtNewLine,
|
|
NewPrivatSectionInsertPos,NewPrivatSectionInsertPos,
|
|
GetIndentStr(NewPrivatSectionIndent)+
|
|
ASourceChangeCache.BeautifyCodeOptions.BeautifyKeyWord('private'));
|
|
end;
|
|
end;
|
|
|
|
InsertNewClassParts(ncpPrivateVars);
|
|
InsertNewClassParts(ncpPrivateProcs);
|
|
|
|
if PublishedNeeded then begin
|
|
ASourceChangeCache.Replace(gtNewLine,gtNewLine,
|
|
NewPrivatSectionInsertPos,NewPrivatSectionInsertPos,
|
|
GetIndentStr(NewPrivatSectionIndent)+
|
|
ASourceChangeCache.BeautifyCodeOptions.BeautifyKeyWord('published'));
|
|
end;
|
|
|
|
InsertNewClassParts(ncpPublishedVars);
|
|
InsertNewClassParts(ncpPublishedProcs);
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.AddNewPropertyAccessMethodsToClassProcs(
|
|
ClassProcs: TAVLTree; const TheClassName: string);
|
|
var ANodeExt: TCodeTreeNodeExtension;
|
|
NewNodeExt: TCodeTreeNodeExtension;
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.AddNewPropertyAccessMethodsToClassProcs]');
|
|
{$ENDIF}
|
|
// add new property access methods to ClassProcs
|
|
ANodeExt:=FirstInsert;
|
|
while ANodeExt<>nil do begin
|
|
if not NodeExtIsVariable(ANodeExt) then begin
|
|
if FindNodeInTree(ClassProcs,ANodeExt.Txt)=nil then begin
|
|
NewNodeExt:=TCodeTreeNodeExtension.Create;
|
|
with NewNodeExt do begin
|
|
Txt:=UpperCaseStr(TheClassName)+'.'
|
|
+ANodeExt.Txt; // Name+ParamTypeList
|
|
ExtTxt1:=ASourceChangeCache.BeautifyCodeOptions.AddClassAndNameToProc(
|
|
ANodeExt.ExtTxt1,TheClassName,''); // complete proc head code
|
|
ExtTxt3:=ANodeExt.ExtTxt3;
|
|
Position:=ANodeExt.Position;
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' Txt="',Txt,'"');
|
|
writeln(' ExtTxt1="',ExtTxt1,'"');
|
|
writeln(' ExtTxt3="',ExtTxt3,'"');
|
|
{$ENDIF}
|
|
end;
|
|
ClassProcs.Add(NewNodeExt);
|
|
end;
|
|
end;
|
|
ANodeExt:=ANodeExt.Next;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeCompletionCodeTool.CheckForOverrideAndAddInheritedCode(
|
|
ClassProcs: TAVLTree);
|
|
// check for 'override' directive and add 'inherited' code to body
|
|
var AnAVLNode: TAVLTreeNode;
|
|
ANodeExt: TCodeTreeNodeExtension;
|
|
ProcCode, ProcCall: string;
|
|
ProcNode: TCodeTreeNode;
|
|
i: integer;
|
|
BeautifyCodeOptions: TBeautifyCodeOptions;
|
|
begin
|
|
if not AddInheritedCodeToOverrideMethod then exit;
|
|
{$IFDEF CTDEBUG}
|
|
writeln('[TCodeCompletionCodeTool.CheckForOverrideAndAddInheritedCode]');
|
|
{$ENDIF}
|
|
BeautifyCodeOptions:=ASourceChangeCache.BeautifyCodeOptions;
|
|
AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
|
|
ProcNode:=ANodeExt.Node;
|
|
if (ProcNode<>nil) and (ANodeExt.ExtTxt3='')
|
|
and (ProcNodeHasSpecifier(ProcNode,psOVERRIDE)) then begin
|
|
ProcCode:=ExtractProcHead(ProcNode,[phpWithStart,phpWithoutClassKeyword,
|
|
phpAddClassname,phpWithVarModifiers,phpWithParameterNames,
|
|
phpWithResultType]);
|
|
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:=BeautifyCodeOptions.BeautifyIdentifier('Result')+':='+ProcCall;
|
|
ProcCode:=ProcCode+BeautifyCodeOptions.LineEnd
|
|
+'begin'+BeautifyCodeOptions.LineEnd
|
|
+GetIndentStr(BeautifyCodeOptions.Indent)
|
|
+ProcCall+BeautifyCodeOptions.LineEnd
|
|
+'end;';
|
|
ANodeExt.ExtTxt3:=ProcCode;
|
|
end;
|
|
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
end;
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.CreateMissingProcBodies: boolean;
|
|
var
|
|
Indent, InsertPos: integer;
|
|
TheClassName: string;
|
|
|
|
procedure InsertProcBody(ANodeExt: TCodeTreeNodeExtension);
|
|
var ProcCode: string;
|
|
begin
|
|
if ANodeExt.ExtTxt3<>'' then
|
|
ProcCode:=ANodeExt.ExtTxt3
|
|
else
|
|
ProcCode:=ANodeExt.ExtTxt1;
|
|
ProcCode:=ASourceChangeCache.BeautifyCodeOptions.AddClassAndNameToProc(
|
|
ProcCode,TheClassName,'');
|
|
{$IFDEF CTDEBUG}
|
|
writeln('CreateMissingProcBodies InsertProcBody ',TheClassName,' "',ProcCode,'"');
|
|
{$ENDIF}
|
|
ProcCode:=ASourceChangeCache.BeautifyCodeOptions.BeautifyProc(
|
|
ProcCode,Indent,ANodeExt.ExtTxt3='');
|
|
ASourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
|
|
ProcCode);
|
|
if JumpToProcName='' then begin
|
|
// remember one proc body to jump to after the completion
|
|
JumpToProcName:=ANodeExt.Txt;
|
|
if System.Pos('.',JumpToProcName)<1 then
|
|
JumpToProcName:=UpperCaseStr(TheClassName)+'.'+JumpToProcName;
|
|
{$IFDEF CTDEBUG}
|
|
writeln('CreateMissingProcBodies JumpToProcName="',JumpToProcName,'"');
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure CreateCodeForMissingProcBody(TheNodeExt: TCodeTreeNodeExtension);
|
|
var
|
|
ANode: TCodeTreeNode;
|
|
ProcCode: string;
|
|
begin
|
|
if (TheNodeExt.ExtTxt1='') and (TheNodeExt.ExtTxt3='') then begin
|
|
ANode:=TheNodeExt.Node;
|
|
if (ANode<>nil) and (ANode.Desc=ctnProcedure) then begin
|
|
ProcCode:=ExtractProcHead(ANode,[phpWithStart,
|
|
phpWithoutClassKeyword,phpAddClassname,
|
|
phpWithParameterNames,phpWithResultType,phpWithVarModifiers]);
|
|
TheNodeExt.ExtTxt3:=ASourceChangeCache.BeautifyCodeOptions.BeautifyProc(
|
|
ProcCode,Indent,true);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
ProcBodyNodes, ClassProcs: TAVLTree;
|
|
ANodeExt, ANodeExt2: TCodeTreeNodeExtension;
|
|
ExistingNode, MissingNode, AnAVLNode, NextAVLNode,
|
|
NearestAVLNode: TAVLTreeNode;
|
|
cmp, MissingNodePosition: integer;
|
|
FirstExistingProcBody, LastExistingProcBody, ImplementationNode,
|
|
ANode, ANode2, TypeSectionNode: TCodeTreeNode;
|
|
ClassStartComment, s: string;
|
|
Caret1, Caret2: TCodeXYPosition;
|
|
MethodInsertPolicy: TMethodInsertPolicy;
|
|
NearestNodeValid: boolean;
|
|
|
|
procedure GatherExistingClassProcBodies;
|
|
begin
|
|
TypeSectionNode:=ClassNode.Parent;
|
|
if (TypeSectionNode<>nil) and (TypeSectionNode.Parent<>nil)
|
|
and (TypeSectionNode.Parent.Desc=ctnTypeSection) then
|
|
TypeSectionNode:=TypeSectionNode.Parent;
|
|
ClassProcs:=nil;
|
|
ProcBodyNodes:=GatherProcNodes(TypeSectionNode,
|
|
[phpInUpperCase,phpIgnoreForwards,phpOnlyWithClassname],
|
|
ExtractClassName(ClassNode,true));
|
|
end;
|
|
|
|
procedure FindTopMostAndBottomMostProcCodies;
|
|
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;
|
|
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 CompareTextIgnoringSpace(ANodeExt.Txt,ANodeExt2.Txt,false)=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;
|
|
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 '+Caret2.Code.Filename;
|
|
MoveCursorToNodeStart(ANode.FirstChild);
|
|
RaiseException('procedure redefined (first at '+s+')');
|
|
end;
|
|
end;
|
|
AnAVLNode:=NextAVLNode;
|
|
end;
|
|
end;
|
|
|
|
procedure RemoveAbstractMethods;
|
|
begin
|
|
AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
NextAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
ANodeExt:=TCodeTreeNodeExtension(AnAVLNode.Data);
|
|
ANode:=ANodeExt.Node;
|
|
if (ANode<>nil) and (ANode.Desc=ctnProcedure)
|
|
and ProcNodeHasSpecifier(ANode,psABSTRACT) then begin
|
|
ClassProcs.Delete(AnAVLNode);
|
|
end;
|
|
AnAVLNode:=NextAVLNode;
|
|
end;
|
|
end;
|
|
|
|
procedure FindInsertPointForNewClass;
|
|
begin
|
|
if NodeHasParentOfType(ClassNode,ctnInterface) then begin
|
|
// class is in interface section
|
|
// -> insert at the end of the implementation section
|
|
ImplementationNode:=FindImplementationNode;
|
|
if ImplementationNode=nil then
|
|
RaiseException(ctsImplementationNodeNotFound);
|
|
Indent:=GetLineIndent(Src,ImplementationNode.StartPos);
|
|
if (ImplementationNode.LastChild=nil)
|
|
or (ImplementationNode.LastChild.Desc<>ctnBeginBlock) then
|
|
InsertPos:=ImplementationNode.EndPos
|
|
else begin
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(
|
|
ImplementationNode.LastChild.StartPos);
|
|
end;
|
|
end else begin
|
|
// class is not in interface section
|
|
// -> insert at the end of the type section
|
|
ANode:=ClassNode.Parent; // type definition
|
|
if ANode=nil then
|
|
RaiseException(ctsClassNodeWithoutParentNode);
|
|
if ANode.Parent.Desc=ctnTypeSection then
|
|
ANode:=ANode.Parent; // type section
|
|
if ANode=nil then
|
|
RaiseException(ctsTypeSectionOfClassNotFound);
|
|
Indent:=GetLineIndent(Src,ANode.StartPos);
|
|
InsertPos:=ANode.EndPos;
|
|
end;
|
|
end;
|
|
|
|
procedure InsertClassComment;
|
|
begin
|
|
// insert class comment
|
|
if ClassProcs.Count>0 then begin
|
|
ClassStartComment:=GetIndentStr(Indent)
|
|
+'{ '+ExtractClassName(ClassNode,false)+' }';
|
|
ASourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
|
|
ClassStartComment);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CreateMissingProcBodies Gather existing method bodies ... ');
|
|
{$ENDIF}
|
|
Result:=false;
|
|
MethodInsertPolicy:=ASourceChangeCache.BeautifyCodeOptions.MethodInsertPolicy;
|
|
// gather existing class proc bodies
|
|
GatherExistingClassProcBodies;
|
|
try
|
|
// find topmost and bottommost proc body
|
|
FindTopMostAndBottomMostProcCodies;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CreateMissingProcBodies Gather existing method declarations ... ');
|
|
{$ENDIF}
|
|
TheClassName:=ExtractClassName(ClassNode,false);
|
|
|
|
// gather existing class proc definitions
|
|
ClassProcs:=GatherProcNodes(StartNode,[phpInUpperCase,phpAddClassName],
|
|
ExtractClassName(ClassNode,true));
|
|
|
|
// check for double defined methods in ClassProcs
|
|
CheckForDoubleDefinedMethods;
|
|
|
|
// remove abstract methods
|
|
RemoveAbstractMethods;
|
|
|
|
CurNode:=FirstExistingProcBody;
|
|
|
|
{AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
writeln(' AAA ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
|
|
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
end;}
|
|
|
|
AddNewPropertyAccessMethodsToClassProcs(ClassProcs,TheClassName);
|
|
|
|
{AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
writeln(' BBB ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
|
|
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
end;}
|
|
|
|
CheckForOverrideAndAddInheritedCode(ClassProcs);
|
|
|
|
{AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
writeln(' BBB ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
|
|
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
end;}
|
|
|
|
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;
|
|
|
|
{AnAVLNode:=ClassProcs.FindLowest;
|
|
while AnAVLNode<>nil do begin
|
|
writeln(' CCC ',TCodeTreeNodeExtension(AnAVLNode.Data).Txt);
|
|
AnAVLNode:=ClassProcs.FindSuccessor(AnAVLNode);
|
|
end;}
|
|
|
|
// search for missing proc bodies
|
|
if (ProcBodyNodes.Count=0) then begin
|
|
// there were no old proc bodies of the class -> start class
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CreateMissingProcBodies Starting class in implementation ');
|
|
{$ENDIF}
|
|
FindInsertPointForNewClass;
|
|
InsertClassComment;
|
|
|
|
// insert all proc bodies
|
|
MissingNode:=ClassProcs.FindHighest;
|
|
while (MissingNode<>nil) do begin
|
|
ANodeExt:=TCodeTreeNodeExtension(MissingNode.Data);
|
|
CreateCodeForMissingProcBody(ANodeExt);
|
|
InsertProcBody(ANodeExt);
|
|
MissingNode:=ProcBodyNodes.FindPrecessor(MissingNode);
|
|
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
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CreateMissingProcBodies Insert missing bodies between existing ... ClassProcs.Count=',ClassProcs.Count);
|
|
{$ENDIF}
|
|
|
|
// set default insert position
|
|
Indent:=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
|
|
ExistingNode:=ProcBodyNodes.Find(MissingNode.Data);
|
|
if ExistingNode=nil then begin
|
|
ANodeExt:=TCodeTreeNodeExtension(MissingNode.Data);
|
|
// MissingNode does not have a body -> insert proc body
|
|
case MethodInsertPolicy of
|
|
mipAlphabetically:
|
|
begin
|
|
// search alphabetically nearest proc body
|
|
ExistingNode:=ProcBodyNodes.FindNearest(MissingNode.Data);
|
|
cmp:=CompareCodeTreeNodeExt(ExistingNode.Data,MissingNode.Data);
|
|
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:=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:=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:=GetLineIndent(Src,ANode.StartPos);
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
|
|
end;
|
|
end;
|
|
end;
|
|
CreateCodeForMissingProcBody(ANodeExt);
|
|
InsertProcBody(ANodeExt);
|
|
end;
|
|
MissingNode:=ProcBodyNodes.FindPrecessor(MissingNode);
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
finally
|
|
if ClassProcs<>nil then begin
|
|
ClassProcs.FreeAndClear;
|
|
ClassProcs.Free;
|
|
end;
|
|
ProcBodyNodes.FreeAndClear;
|
|
ProcBodyNodes.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCodeCompletionCodeTool.CompleteCode(CursorPos: TCodeXYPosition;
|
|
var NewPos: TCodeXYPosition; var NewTopLine: integer;
|
|
SourceChangeCache: TSourceChangeCache): boolean;
|
|
var CleanCursorPos, Indent, insertPos: integer;
|
|
CursorNode, ProcNode, ImplementationNode, SectionNode, AClassNode,
|
|
ANode: TCodeTreeNode;
|
|
ProcCode: string;
|
|
|
|
procedure CompleteClass;
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode In-a-class ',NodeDescriptionAsString(AClassNode.Desc));
|
|
{$ENDIF}
|
|
// cursor is in class/object definition
|
|
if (CursorNode.SubDesc and ctnsForwardDeclaration)>0 then exit;
|
|
// parse class and build CodeTreeNodes for all properties/methods
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode C ',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}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Complete Properties ... ');
|
|
{$ENDIF}
|
|
SectionNode:=ClassNode.FirstChild;
|
|
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(ctsUnableToCompleteProperty);
|
|
end;
|
|
ANode:=ANode.NextBrother;
|
|
end;
|
|
SectionNode:=SectionNode.NextBrother;
|
|
end;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Insert new variables and methods ... ');
|
|
{$ENDIF}
|
|
// insert all new variables and procs definitions
|
|
if not InsertAllNewClassParts then
|
|
RaiseException(ctsErrorDuringInsertingNewClassParts);
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Insert new method bodies ... ');
|
|
{$ENDIF}
|
|
// insert all missing proc bodies
|
|
if not CreateMissingProcBodies then
|
|
RaiseException(ctsErrorDuringCreationOfNewProcBodies);
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Apply ... ');
|
|
{$ENDIF}
|
|
// apply the changes and jump to first new proc body
|
|
if not SourceChangeCache.Apply then
|
|
RaiseException(ctsUnableToApplyChanges);
|
|
|
|
if JumpToProcName<>'' then begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Jump to new proc body ... "',JumpToProcName,'"');
|
|
{$ENDIF}
|
|
// there was a new proc body
|
|
// -> find it and jump to
|
|
|
|
// reparse code
|
|
BuildTreeAndGetCleanPos(trAll,CursorPos,CleanCursorPos);
|
|
// find CodeTreeNode at cursor
|
|
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
|
|
|
|
ClassNode:=CursorNode;
|
|
while (ClassNode<>nil) and (ClassNode.Desc<>ctnClass) do
|
|
ClassNode:=ClassNode.Parent;
|
|
if ClassNode=nil then
|
|
RaiseException('oops, I lost your class');
|
|
ANode:=ClassNode.Parent;
|
|
if ANode=nil then
|
|
RaiseException(ctsClassNodeWithoutParentNode);
|
|
if (ANode.Parent<>nil) and (ANode.Parent.Desc=ctnTypeSection) then
|
|
ANode:=ANode.Parent;
|
|
ProcNode:=FindProcNode(ANode,JumpToProcName,
|
|
[phpInUpperCase,phpIgnoreForwards]);
|
|
if ProcNode=nil then
|
|
RaiseException(ctsNewProcBodyNotFound);
|
|
Result:=FindJumpPointInProcNode(ProcNode,NewPos,NewTopLine);
|
|
end else begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Adjust Cursor ... ');
|
|
{$ENDIF}
|
|
// there was no new proc body
|
|
// -> adjust cursor
|
|
NewPos:=CursorPos;
|
|
NewPos.Code.AdjustCursor(NewPos.Y,NewPos.X);
|
|
NewTopLine:=NewPos.Y-(VisibleEditorLines div 2);
|
|
if NewTopLine<1 then NewTopLine:=1;
|
|
Result:=true;
|
|
end;
|
|
|
|
finally
|
|
FreeClassInsertionList;
|
|
end;
|
|
end;
|
|
|
|
procedure CompleteForwardProc;
|
|
begin
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode in a forward procedure ... ');
|
|
{$ENDIF}
|
|
|
|
// check if proc already exists
|
|
ProcCode:=ExtractProcHead(ProcNode,[phpInUpperCase]);
|
|
if FindProcNode(FindNextNodeOnSameLvl(ProcNode),ProcCode,
|
|
[phpInUpperCase])<>nil
|
|
then exit;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode Body not found -> create it ... ');
|
|
{$ENDIF}
|
|
// -> create proc body at end of implementation
|
|
|
|
Indent:=GetLineIndent(Src,ImplementationNode.StartPos);
|
|
if (ImplementationNode.LastChild=nil)
|
|
or (ImplementationNode.LastChild.Desc<>ctnBeginBlock) then
|
|
// insert at end of code
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ImplementationNode.EndPos)
|
|
else begin
|
|
// insert in front of main program begin..end.
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(
|
|
ImplementationNode.LastChild.StartPos);
|
|
end;
|
|
|
|
// build nice proc
|
|
ProcCode:=ExtractProcHead(ProcNode,[phpWithStart,phpWithoutClassKeyword,
|
|
phpWithVarModifiers,phpWithParameterNames,phpWithResultType,
|
|
phpWithComments]);
|
|
if ProcCode='' then
|
|
RaiseException('unable to reparse proc node');
|
|
ProcCode:=SourceChangeCache.BeautifyCodeOptions.BeautifyProc(ProcCode,
|
|
Indent,true);
|
|
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
|
|
InsertPos,InsertPos,ProcCode) then
|
|
RaiseException('unable to insert new proc body');
|
|
if not SourceChangeCache.Apply then
|
|
RaiseException('unable to apply changes');
|
|
|
|
// reparse code and find jump point into new proc
|
|
Result:=FindJumpPoint(CursorPos,NewPos,NewTopLine);
|
|
end;
|
|
|
|
function IsEventAssignment: boolean;
|
|
var SearchedClassName: string;
|
|
{ examples:
|
|
Button1.OnClick:=|
|
|
OnClick:=@AnEve|nt
|
|
with Button1 do OnMouseDown:=@|
|
|
|
|
If OnClick is a method then it will 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(var PropertyAtom: TAtomPosition;
|
|
var AssignmentOperator, AddrOperatorPos: integer;
|
|
var UserEventAtom: TAtomPosition;
|
|
var SemicolonPos: integer): boolean;
|
|
begin
|
|
Result:=false;
|
|
|
|
// check if in begin..end block
|
|
if not ((CursorNode.Desc=ctnBeginBlock)
|
|
or CursorNode.HasParentOfType(ctnBeginBlock)) then exit;
|
|
if CursorNode.Desc=ctnBeginBlock then
|
|
BuildSubTreeForBeginBlock(CursorNode);
|
|
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
|
|
// read event name (optional)
|
|
GetIdentStartEndAtPosition(Src,CleanCursorPos,
|
|
UserEventAtom.StartPos,UserEventAtom.EndPos);
|
|
MoveCursorToCleanPos(UserEventAtom.StartPos);
|
|
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;
|
|
AssignmentOperator:=CurPos.StartPos;
|
|
ReadPriorAtom;
|
|
// check event name
|
|
if not AtomIsIdentifier(false) then exit;
|
|
PropertyAtom:=CurPos;
|
|
|
|
// check for semicolon at end of statement
|
|
MoveCursorToCleanPos(UserEventAtom.EndPos);
|
|
ReadNextAtom;
|
|
if AtomIsChar(';') then
|
|
SemicolonPos:=CurPos.StartPos
|
|
else
|
|
SemicolonPos:=-1;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' CheckEventAssignmentSyntax: "',copy(Src,PropertyAtom.StartPos,
|
|
UserEventAtom.EndPos-PropertyAtom.StartPos),'"');
|
|
{$ENDIF}
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
function FindEventTypeAtCursor(PropertyAtom: TAtomPosition;
|
|
var PropertyContext, ProcContext: TFindContext;
|
|
Params: TFindDeclarationParams): boolean;
|
|
begin
|
|
Result:=false;
|
|
// find declaration of property identifier
|
|
Params.ContextNode:=CursorNode;
|
|
MoveCursorToCleanPos(PropertyAtom.StartPos);
|
|
Params.SetIdentifier(Self,@Src[CurPos.StartPos],nil);
|
|
FullTopLvlName:='';
|
|
Params.OnTopLvlIdentifierFound:=@OnTopLvlIdentifierFound;
|
|
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,
|
|
fdfTopLvlResolving,fdfFindVariable]
|
|
+fdfAllClassVisibilities;
|
|
if (not FindDeclarationOfIdentAtCursor(Params))
|
|
or (Params.NewNode.Desc<>ctnProperty) then exit;
|
|
PropertyContext:=CreateFindContext(Params);
|
|
// identifier is property
|
|
// -> check type of property
|
|
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors]
|
|
+fdfAllClassVisibilities;
|
|
ProcContext:=PropertyContext.Tool.FindBaseTypeOfNode(
|
|
Params,PropertyContext.Node);
|
|
if (ProcContext.Node=nil) or (ProcContext.Node.Desc<>ctnProcedureType)
|
|
then
|
|
exit;
|
|
// identifier is property of type proc => this is an event
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateEventFullName(UserEventAtom,
|
|
PropertyAtom: TAtomPosition): string;
|
|
var PropertyName, AClassName: string;
|
|
l: integer;
|
|
begin
|
|
if UserEventAtom.StartPos=UserEventAtom.EndPos then begin
|
|
Result:=FullTopLvlName;
|
|
l:=PropertyAtom.EndPos-PropertyAtom.StartPos;
|
|
PropertyName:=copy(Src,PropertyAtom.StartPos,l);
|
|
if AnsiCompareText(PropertyName,RightStr(Result,l))<>0 then
|
|
Result:=Result+PropertyName;
|
|
if AnsiCompareText(PropertyName,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(PropertyName,2))='ON')
|
|
and (AnsiCompareText(RightStr(Result,l),PropertyName)=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}
|
|
writeln('CreateEventFullName "',Result,'"');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function FindClassAndProcNode: boolean;
|
|
begin
|
|
Result:=false;
|
|
ProcNode:=CursorNode;
|
|
while (ProcNode<>nil) and (ProcNode.Desc<>ctnProcedure) do
|
|
ProcNode:=ProcNode.Parent;
|
|
if (ProcNode=nil) then exit;
|
|
SearchedClassname:=ExtractClassNameOfProcNode(ProcNode);
|
|
if SearchedClassname='' then exit;
|
|
ANode:=FindFirstNodeOnSameLvl(ProcNode);
|
|
if (ANode=nil) then exit;
|
|
// search class node
|
|
AClassNode:=FindClassNode(ANode,UpperCaseStr(SearchedClassName),
|
|
true,false);
|
|
if AClassNode=nil then exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function AddEventAndCompleteAssignment(const AnEventName: string;
|
|
ProcContext: TFindContext;
|
|
AssignmentOperator, AddrOperatorPos, SemicolonPos: integer;
|
|
UserEventAtom: TAtomPosition;
|
|
var MethodDefinition: string; var MethodAttr: TProcHeadAttributes
|
|
): boolean;
|
|
var RValue, CleanMethodDefinition: string;
|
|
StartInsertPos, EndInsertPos: integer;
|
|
begin
|
|
Result:=false;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: 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}
|
|
writeln(' IsEventAssignment: Initializing CodeCompletion...');
|
|
{$ENDIF}
|
|
// initialize class for code completion
|
|
CodeCompleteClassNode:=AClassNode;
|
|
CodeCompleteSrcChgCache:=SourceChangeCache;
|
|
|
|
// insert new published method to class
|
|
MethodAttr:=[phpWithStart, phpWithoutClassKeyword, phpWithVarModifiers,
|
|
phpWithParameterNames,phpWithDefaultValues,phpWithResultType];
|
|
MethodDefinition:=TrimCodeSpace(ProcContext.Tool.ExtractProcHead(
|
|
ProcContext.Node,
|
|
MethodAttr+[phpWithoutClassName,phpWithoutName]));
|
|
MethodDefinition:=SourceChangeCache.BeautifyCodeOptions.
|
|
AddClassAndNameToProc(MethodDefinition, '', AnEventName);
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: Add Method To Class...');
|
|
{$ENDIF}
|
|
if not ProcExistsInCodeCompleteClass(CleanMethodDefinition) then begin
|
|
// insert method definition into class
|
|
AddClassInsertion(nil, CleanMethodDefinition, MethodDefinition,
|
|
AnEventName, '', ncpPublishedProcs);
|
|
end;
|
|
MethodDefinition:=SourceChangeCache.BeautifyCodeOptions.
|
|
AddClassAndNameToProc(MethodDefinition,
|
|
ExtractClassName(AClassNode,false), AnEventName);
|
|
if not InsertAllNewClassParts then
|
|
RaiseException(ctsErrorDuringInsertingNewClassParts);
|
|
|
|
// insert all missing proc bodies
|
|
if not CreateMissingProcBodies then
|
|
RaiseException(ctsErrorDuringCreationOfNewProcBodies);
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: 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;
|
|
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;
|
|
SourceChangeCache.Replace(gtNone,gtNewLine,StartInsertPos,EndInsertPos,
|
|
RValue);
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: Applying changes...');
|
|
{$ENDIF}
|
|
// apply the changes
|
|
if not SourceChangeCache.Apply then
|
|
RaiseException(ctsUnableToApplyChanges);
|
|
Result:=true;
|
|
end;
|
|
|
|
// function IsEventAssignment: boolean
|
|
var
|
|
UserEventAtom, PropertyAtom: TAtomPosition;
|
|
AssignmentOperator, AddrOperatorPos, SemicolonPos: integer;
|
|
Params: TFindDeclarationParams;
|
|
PropertyContext, ProcContext: TFindContext;
|
|
FullEventName, AMethodDefinition: string;
|
|
AMethodAttr: TProcHeadAttributes;
|
|
begin
|
|
Result:=false;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: CheckEventAssignmentSyntax...');
|
|
{$ENDIF}
|
|
// check assigment syntax
|
|
if not CheckEventAssignmentSyntax(PropertyAtom, AssignmentOperator,
|
|
AddrOperatorPos, UserEventAtom, SemicolonPos)
|
|
then
|
|
exit;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: find class of method...');
|
|
{$ENDIF}
|
|
if not FindClassAndProcNode then exit;
|
|
|
|
ActivateGlobalWriteLock;
|
|
Params:=TFindDeclarationParams.Create;
|
|
try
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: FindEventTypeAtCursor...');
|
|
{$ENDIF}
|
|
// check if identifier is event property and build
|
|
Result:=FindEventTypeAtCursor(PropertyAtom,PropertyContext,ProcContext,
|
|
Params);
|
|
if not Result then exit;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: CreateEventFullName... UserEventAtom.StartPos=',UserEventAtom.StartPos);
|
|
{$ENDIF}
|
|
// create a nice event name
|
|
FullEventName:=CreateEventFullName(UserEventAtom,PropertyAtom);
|
|
if FullEventName='' then exit;
|
|
|
|
finally
|
|
Params.Free;
|
|
DeactivateGlobalWriteLock;
|
|
end;
|
|
|
|
// add published method and method body and right side of assignment
|
|
if not AddEventAndCompleteAssignment(FullEventName,ProcContext,
|
|
AssignmentOperator,AddrOperatorPos,SemicolonPos,UserEventAtom,
|
|
AMethodDefinition, AMethodAttr)
|
|
then
|
|
RaiseException('IsEventAssignment Internal Error 1');
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln(' IsEventAssignment: jumping to new method body...');
|
|
{$ENDIF}
|
|
// jump to new method body
|
|
if not JumpToMethod(AMethodDefinition,AMethodAttr,NewPos,NewTopLine,false)
|
|
then
|
|
RaiseException('IsEventAssignment Internal Error 2');
|
|
|
|
CompleteCode:=true;
|
|
end;
|
|
|
|
begin
|
|
Result:=false;
|
|
if (SourceChangeCache=nil) then
|
|
RaiseException('need a SourceChangeCache');
|
|
// in a class or in a forward proc?
|
|
BuildTreeAndGetCleanPos(trAll,CursorPos, CleanCursorPos);
|
|
// find CodeTreeNode at cursor
|
|
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
|
|
CodeCompleteSrcChgCache:=SourceChangeCache;
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode A CleanCursorPos=',CleanCursorPos,' NodeDesc=',NodeDescriptionAsString(CursorNode.Desc));
|
|
{$ENDIF}
|
|
ImplementationNode:=FindImplementationNode;
|
|
if ImplementationNode=nil then ImplementationNode:=Tree.Root;
|
|
|
|
// test if in a class
|
|
AClassNode:=CursorNode;
|
|
while (AClassNode<>nil) and (AClassNode.Desc<>ctnClass) do
|
|
AClassNode:=AClassNode.Parent;
|
|
if AClassNode<>nil then begin
|
|
CompleteClass;
|
|
exit;
|
|
end;
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode not in-a-class ... ');
|
|
{$ENDIF}
|
|
|
|
// test if forward proc
|
|
ProcNode:=CursorNode;
|
|
if ProcNode.Desc=ctnProcedureHead then ProcNode:=ProcNode.Parent;
|
|
if (ProcNode.Desc=ctnProcedure)
|
|
and ((ProcNode.SubDesc and ctnsForwardDeclaration)>0) then begin
|
|
// Node is forward Proc
|
|
CompleteForwardProc;
|
|
exit;
|
|
end;
|
|
|
|
// test if Event assignment
|
|
if IsEventAssignment then exit;
|
|
|
|
{$IFDEF CTDEBUG}
|
|
writeln('TCodeCompletionCodeTool.CompleteCode nothing to complete ... ');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TCodeCompletionCodeTool.Create;
|
|
begin
|
|
inherited Create;
|
|
FSetPropertyVariablename:='AValue';
|
|
FCompleteProperties:=true;
|
|
FAddInheritedCodeToOverrideMethod:=true;
|
|
end;
|
|
|
|
|
|
end.
|
|
|