mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 00:03:46 +02:00
1357 lines
48 KiB
ObjectPascal
1357 lines
48 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:
|
|
TExtractProcTool enhances TCodeCompletionCodeTool.
|
|
TExtractProcTool provides functions to extract statements from procedures
|
|
and to move them to new procedure, sub procedures or methods. Parameter
|
|
list is auto created and local variables are automatically created and/or
|
|
removed.
|
|
Note: Extracting a procedure from a method needs manual fixing of used
|
|
method variables.
|
|
|
|
ToDo:
|
|
- check if selection bounds on statement bounds
|
|
- with statements
|
|
}
|
|
unit ExtractProcTool;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{ $define CTDEBUG}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FileProcs, CodeToolsStrConsts, CodeTree, CodeAtom,
|
|
CustomCodeTool,
|
|
PascalParserTool, CodeCompletionTool, KeywordFuncLists, BasicCodeTools,
|
|
LinkScanner, AVL_Tree, SourceChanger,
|
|
FindDeclarationTool;
|
|
|
|
type
|
|
TExtractedProcVariableType = (
|
|
epvtParameter,
|
|
epvtLocalVar
|
|
//epvtExternVar // variable is defined outside (e.g. a global variable or a class member)
|
|
);
|
|
|
|
TExtractedProcVariable = class
|
|
public
|
|
Node: TCodeTreeNode;
|
|
Tool: TFindDeclarationTool;
|
|
VarType: TExtractedProcVariableType;
|
|
ReadInSelection: boolean;
|
|
WriteInSelection: boolean;
|
|
UsedInNonSelection: boolean;
|
|
ReadAfterSelection: boolean;
|
|
ReadAfterSelectionValid: boolean;
|
|
RemovedFromOldProc: boolean;
|
|
function UsedInSelection: boolean;
|
|
end;
|
|
|
|
{ TExtractProcTool }
|
|
|
|
TExtractProcType = (
|
|
eptProcedure,
|
|
eptProcedureWithInterface,
|
|
eptSubProcedure,
|
|
eptSubProcedureSameLvl,
|
|
eptPrivateMethod,
|
|
eptProtectedMethod,
|
|
eptPublicMethod,
|
|
eptPublishedMethod
|
|
);
|
|
|
|
TExtractProcTool = class(TCodeCompletionCodeTool)
|
|
protected
|
|
function ScanNodesForVariables(const StartPos, EndPos: TCodeXYPosition;
|
|
out BlockStartPos, BlockEndPos: integer; // the selection
|
|
out ProcNode: TCodeTreeNode;
|
|
VarTree: TAVLTree; // tree of TExtractedProcVariable
|
|
IgnoreIdentifiers: TAVLTree; // tree of PCodeXYPosition
|
|
MissingIdentifiers: TAVLTree // tree of PCodeXYPosition
|
|
): boolean;
|
|
function InitExtractProc(const StartPos, EndPos: TCodeXYPosition;
|
|
out MethodPossible, SubProcSameLvlPossible: boolean): boolean;
|
|
public
|
|
function CheckExtractProc(const StartPos, EndPos: TCodeXYPosition;
|
|
out MethodPossible, SubProcSameLvlPossible: boolean;
|
|
out MissingIdentifiers: TAVLTree; // tree of PCodeXYPosition
|
|
VarTree: TAVLTree = nil // tree of TExtractedProcVariable
|
|
): boolean;
|
|
function ExtractProc(const StartPos, EndPos: TCodeXYPosition;
|
|
ProcType: TExtractProcType; const ProcName: string;
|
|
IgnoreIdentifiers: TAVLTree; // tree of PCodeXYPosition
|
|
out NewPos: TCodeXYPosition; out NewTopLine: integer;
|
|
SourceChangeCache: TSourceChangeCache;
|
|
FunctionResultVariableStartPos: integer = 0): boolean;
|
|
|
|
procedure CalcMemSize(Stats: TCTMemStats); override;
|
|
end;
|
|
|
|
const
|
|
ExtractProcTypeNames: array[TExtractProcType] of string = (
|
|
'Procedure',
|
|
'ProcedureWithInterface',
|
|
'SubProcedure',
|
|
'SubProcedureSameLvl',
|
|
'PrivateMethod',
|
|
'ProtectedMethod',
|
|
'PublicMethod',
|
|
'PublishedMethod'
|
|
);
|
|
|
|
function CreateExtractProcVariableTree: TAVLTree;
|
|
procedure ClearExtractProcVariableTree(VarTree: TAVLTree; FreeTree: boolean);
|
|
|
|
implementation
|
|
|
|
function CompareExtractedProcVariables(V1, V2: TExtractedProcVariable): integer;
|
|
var
|
|
cmp: Integer;
|
|
begin
|
|
cmp:=V2.Node.StartPos-V1.Node.StartPos;
|
|
if cmp<0 then
|
|
Result:=-1
|
|
else if cmp>0 then
|
|
Result:=1
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
function CompareNodeWithExtractedProcVariable(Node: TCodeTreeNode;
|
|
V: TExtractedProcVariable): integer;
|
|
var
|
|
cmp: Integer;
|
|
begin
|
|
cmp:=V.Node.StartPos-Node.StartPos;
|
|
if cmp<0 then
|
|
Result:=-1
|
|
else if cmp>0 then
|
|
Result:=1
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
function CreateExtractProcVariableTree: TAVLTree;
|
|
begin
|
|
Result:=TAVLTree.Create(TListSortCompare(@CompareExtractedProcVariables));
|
|
end;
|
|
|
|
procedure ClearExtractProcVariableTree(VarTree: TAVLTree; FreeTree: boolean);
|
|
begin
|
|
if VarTree=nil then exit;
|
|
VarTree.FreeAndClear;
|
|
if FreeTree then
|
|
VarTree.Free;
|
|
end;
|
|
|
|
{ TExtractedProcVariable }
|
|
|
|
function TExtractedProcVariable.UsedInSelection: boolean;
|
|
begin
|
|
Result:=ReadInSelection or WriteInSelection;
|
|
end;
|
|
|
|
{ TExtractProcTool }
|
|
|
|
function TExtractProcTool.InitExtractProc(const StartPos,
|
|
EndPos: TCodeXYPosition;
|
|
out MethodPossible, SubProcSameLvlPossible: boolean): boolean;
|
|
var
|
|
CleanStartPos, CleanEndPos: integer;
|
|
CursorNode: TCodeTreeNode;
|
|
BeginBlockNode: TCodeTreeNode;
|
|
BlockCleanEnd: Integer;
|
|
BlockCleanStart: LongInt;
|
|
ANode: TCodeTreeNode;
|
|
ProcLvl: Integer;
|
|
begin
|
|
Result:=false;
|
|
MethodPossible:=false;
|
|
SubProcSameLvlPossible:=false;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InitExtractProc syntax and cursor check ..');
|
|
{$ENDIF}
|
|
// check syntax
|
|
BuildTreeAndGetCleanPos(trAll,StartPos,CleanStartPos,[]);
|
|
if CaretToCleanPos(EndPos,CleanEndPos)<>0 then exit;
|
|
if CleanStartPos>=CleanEndPos then exit;
|
|
{$IFDEF CTDebug}
|
|
debugln('TExtractProcTool.InitExtractProc Selection="',copy(Src,CleanStartPos,CleanEndPos-CleanStartPos),'"');
|
|
DebugLn('TExtractProcTool.InitExtractProc node check ..');
|
|
{$ENDIF}
|
|
// check if in a Begin..End block
|
|
CursorNode:=FindDeepestNodeAtPos(CleanStartPos,true);
|
|
if CursorNode=nil then exit;
|
|
BeginBlockNode:=CursorNode.GetNodeOfType(ctnBeginBlock);
|
|
if BeginBlockNode=nil then exit;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InitExtractProc Start/End check ..');
|
|
{$ENDIF}
|
|
// check if Start and End on same block level
|
|
MoveCursorToNodeStart(CursorNode);
|
|
// check every block in selection
|
|
while true do begin
|
|
ReadNextAtom;
|
|
if (CurPos.EndPos>CleanEndPos) or (CurPos.StartPos>SrcLen)
|
|
or (CurPos.StartPos>CursorNode.EndPos) then
|
|
break;
|
|
//debugln('TExtractProcTool.InitExtractProc A "',GetAtom,'"');
|
|
if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
|
|
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)
|
|
then begin
|
|
//debugln('TExtractProcTool.InitExtractProc WordIsBlockStatementStart "',GetAtom,'"');
|
|
BlockCleanStart:=CurPos.StartPos;
|
|
if not ReadTilBlockStatementEnd(true) then exit;
|
|
BlockCleanEnd:=CurPos.EndPos;
|
|
debugln(copy(Src,BlockCleanStart,BlockCleanEnd-BlockCleanStart));
|
|
//debugln('TExtractProcTool.InitExtractProc BlockEnd "',GetAtom,'" BlockCleanEnd=',dbgs(BlockCleanEnd),' CleanEndPos=',dbgs(CleanEndPos),' Result=',dbgs(Result),' BlockStartedInside=',dbgs(BlockCleanStart>=CleanStartPos));
|
|
if BlockCleanStart<CleanStartPos then begin
|
|
// this block started outside the selection
|
|
// -> it should end outside
|
|
if (BlockCleanEnd>=CleanStartPos) and (BlockCleanEnd<CleanEndPos) then
|
|
begin
|
|
// block overlaps selection
|
|
exit;
|
|
end;
|
|
if BlockCleanEnd>=CleanEndPos then begin
|
|
// set cursor back to block start
|
|
MoveCursorToCleanPos(BlockCleanStart);
|
|
ReadNextAtom;
|
|
end;
|
|
end else begin
|
|
// this block started inside the selection
|
|
// -> it should end inside
|
|
if (BlockCleanEnd>CleanEndPos) then begin
|
|
// block overlaps selection
|
|
exit;
|
|
end;
|
|
end;
|
|
//debugln('TExtractProcTool.InitExtractProc Block ok');
|
|
end
|
|
else if WordIsBlockStatementEnd.DoItCaseInsensitive(Src,
|
|
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)
|
|
then begin
|
|
// a block ended inside, that started outside
|
|
exit;
|
|
end
|
|
else if WordIsBlockStatementMiddle.DoItCaseInsensitive(Src,
|
|
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)
|
|
then begin
|
|
// a block ended inside, that started outside
|
|
exit;
|
|
end;
|
|
end;
|
|
// check if start not in a statement
|
|
// ToDo
|
|
// check if end not in a statement
|
|
// ToDo
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InitExtractProc Method check ..');
|
|
{$ENDIF}
|
|
// check if in a method body
|
|
ANode:=CursorNode;
|
|
ProcLvl:=0;
|
|
while ANode<>nil do begin
|
|
if (ANode.Desc=ctnProcedure) then begin
|
|
inc(ProcLvl);
|
|
if NodeIsInAMethod(ANode) then begin
|
|
MethodPossible:=true;
|
|
end;
|
|
end;
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
SubProcSameLvlPossible:=(ProcLvl>1);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InitExtractProc END');
|
|
{$ENDIF}
|
|
Result:=true;
|
|
end;
|
|
|
|
function TExtractProcTool.CheckExtractProc(const StartPos,
|
|
EndPos: TCodeXYPosition; out MethodPossible, SubProcSameLvlPossible: boolean;
|
|
out MissingIdentifiers: TAVLTree; VarTree: TAVLTree): boolean;
|
|
var
|
|
BlockStartPos: integer;
|
|
BlockEndPos: integer;
|
|
ProcNode: TCodeTreeNode;
|
|
begin
|
|
Result:=false;
|
|
MissingIdentifiers:=nil;
|
|
if not InitExtractProc(StartPos,EndPos,MethodPossible,SubProcSameLvlPossible)
|
|
then exit;
|
|
MissingIdentifiers:=CreateTreeOfPCodeXYPosition;
|
|
if not ScanNodesForVariables(StartPos,EndPos,BlockStartPos,BlockEndPos,
|
|
ProcNode,VarTree,nil,MissingIdentifiers) then exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TExtractProcTool.ExtractProc(const StartPos, EndPos: TCodeXYPosition;
|
|
ProcType: TExtractProcType; const ProcName: string;
|
|
IgnoreIdentifiers: TAVLTree; // tree of PCodeXYPosition
|
|
out NewPos: TCodeXYPosition; out NewTopLine: integer;
|
|
SourceChangeCache: TSourceChangeCache;
|
|
FunctionResultVariableStartPos: integer): boolean;
|
|
const
|
|
ShortProcFormat = [phpWithoutClassKeyword];
|
|
{$IFDEF CTDebug}
|
|
ParameterTypeNames: array[TParameterType] of string = (
|
|
'ptNone', 'ptConst', 'ptVar', 'ptOut', 'ptNoSpecifier');
|
|
{$ENDIF}
|
|
type
|
|
TParameterType = (ptNone, ptConst, ptVar, ptOut, ptNoSpecifier);
|
|
var
|
|
BlockStartPos, BlockEndPos: integer; // the selection
|
|
ProcNode: TCodeTreeNode; // the main proc node of the selection
|
|
VarTree: TAVLTree;
|
|
ResultNode: TCodeTreeNode;
|
|
|
|
function FindFunctionResultNode: boolean;
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
begin
|
|
Result:=false;
|
|
ResultNode:=nil;
|
|
if FunctionResultVariableStartPos<1 then exit(true); // create a proc, not a function
|
|
AVLNode:=VarTree.FindLowest;
|
|
while AVLNode<>nil do begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
if ProcVar.Node.StartPos=FunctionResultVariableStartPos then begin
|
|
ProcVar.UsedInNonSelection:=true;
|
|
ProcVar.ReadAfterSelection:=true;
|
|
Result:=true;
|
|
ResultNode:=ProcVar.Node;
|
|
exit;
|
|
end;
|
|
AVLNode:=VarTree.FindSuccessor(AVLNode);
|
|
end;
|
|
end;
|
|
|
|
function ReplaceSelectionWithCall: boolean;
|
|
var
|
|
Indent: Integer;
|
|
CallCode: String;
|
|
ParamListCode: String;
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
begin
|
|
Result:=false;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.ReplaceSelectionWithCall A');
|
|
{$ENDIF}
|
|
Indent:=GetLineIndent(Src,BlockStartPos);
|
|
ParamListCode:='';
|
|
// gather all variables, that are used in the selection and in the rest of
|
|
// the old proc (in front or behind). These are the parameters for the new proc.
|
|
if (VarTree<>nil) and (ProcType<>eptSubProcedure) then begin
|
|
AVLNode:=VarTree.FindLowest;
|
|
while AVLNode<>nil do begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.ReplaceSelectionWithCall B ',GetIdentifier(@Src[ProcVar.Node.StartPos]),
|
|
' ReadInSelection=',dbgs(ProcVar.ReadInSelection),
|
|
' WriteInSelection=',dbgs(ProcVar.WriteInSelection),
|
|
' UsedInNonSelection=',dbgs(ProcVar.UsedInNonSelection),
|
|
' ReadAfterSelection=',dbgs(ProcVar.ReadAfterSelection),
|
|
'');
|
|
{$ENDIF}
|
|
if (ProcVar.UsedInSelection and ProcVar.UsedInNonSelection)
|
|
and (ResultNode<>ProcVar.Node) then begin
|
|
// parameter
|
|
if ParamListCode<>'' then ParamListCode:=ParamListCode+',';
|
|
ParamListCode:=ParamListCode+GetIdentifier(@Src[ProcVar.Node.StartPos]);
|
|
end;
|
|
AVLNode:=VarTree.FindSuccessor(AVLNode);
|
|
end;
|
|
end;
|
|
if ParamListCode<>'' then
|
|
ParamListCode:='('+ParamListCode+')';
|
|
CallCode:=ProcName+ParamListCode+';';
|
|
if ResultNode<>nil then begin
|
|
CallCode:=GetIdentifier(@Src[ResultNode.StartPos])+':='+CallCode;
|
|
end;
|
|
CallCode:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
|
|
CallCode,Indent);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.ReplaceSelectionWithCall C "',CallCode,'" Indent=',dbgs(Indent));
|
|
{$ENDIF}
|
|
SourceChangeCache.Replace(gtNewLine,gtNewLine,BlockStartPos,BlockEndPos,
|
|
CallCode);
|
|
Result:=true;
|
|
end;
|
|
|
|
function DeleteLocalVariable(ProcVar: TExtractedProcVariable): boolean;
|
|
|
|
function VariableNodeShouldBeDeleted(VarNode: TCodeTreeNode;
|
|
out CurProcVar: TExtractedProcVariable): boolean;
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
begin
|
|
CurProcVar:=nil;
|
|
AVLNode:=VarTree.FindKey(VarNode,
|
|
TListSortCompare(@CompareNodeWithExtractedProcVariable));
|
|
if AVLNode=nil then begin
|
|
Result:=false;
|
|
end else begin
|
|
CurProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
Result:=(not CurProcVar.UsedInNonSelection)
|
|
and CurProcVar.UsedInSelection;
|
|
end;
|
|
end;
|
|
|
|
function VarSectionIsEmpty: boolean;
|
|
var
|
|
VarNode: TCodeTreeNode;
|
|
SectionNode: TCodeTreeNode;
|
|
CurProcVar: TExtractedProcVariable;
|
|
begin
|
|
Result:=false;
|
|
SectionNode:=ProcVar.Node;
|
|
if SectionNode.Desc=ctnVarDefinition then
|
|
SectionNode:=SectionNode.Parent;
|
|
if SectionNode.Desc<>ctnVarSection then exit;
|
|
VarNode:=SectionNode.FirstChild;
|
|
while VarNode<>nil do begin
|
|
CurProcVar:=nil;
|
|
if not VariableNodeShouldBeDeleted(VarNode,CurProcVar) then exit;
|
|
if not CurProcVar.RemovedFromOldProc then exit;
|
|
VarNode:=VarNode.NextBrother;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
var
|
|
VarNode: TCodeTreeNode;
|
|
FirstVarNode: TCodeTreeNode;
|
|
LastVarNode: TCodeTreeNode;
|
|
DeleteCompleteDefinition: Boolean;
|
|
DeleteStartPos: Integer;
|
|
DeleteEndPos: Integer;
|
|
CurProcVar: TExtractedProcVariable;
|
|
FrontGap: TGapTyp;
|
|
begin
|
|
Result:=false;
|
|
if not ProcVar.RemovedFromOldProc then begin
|
|
// check all variables of the definition (e.g. 'i,j,k: integer')
|
|
FirstVarNode:=ProcVar.Node;
|
|
while (FirstVarNode.PriorBrother<>nil)
|
|
and (FirstVarNode.PriorBrother.Desc=ctnVarDefinition)
|
|
and (FirstVarNode.PriorBrother.FirstChild=nil) do
|
|
FirstVarNode:=FirstVarNode.PriorBrother;
|
|
LastVarNode:=FirstVarNode;
|
|
while (LastVarNode.NextBrother<>nil)
|
|
and (LastVarNode.NextBrother.Desc=ctnVarDefinition)
|
|
and (LastVarNode.FirstChild=nil) do
|
|
LastVarNode:=LastVarNode.NextBrother;
|
|
VarNode:=FirstVarNode;
|
|
// delete variables
|
|
DeleteCompleteDefinition:=true;
|
|
DeleteStartPos:=0;
|
|
DeleteEndPos:=0;
|
|
repeat
|
|
if VariableNodeShouldBeDeleted(VarNode,CurProcVar) then begin
|
|
// delete variable name and comma
|
|
// if the whole definition is deleted, this is handled behind the
|
|
// loop. Examples:
|
|
// var i, X: integer; -> var i[, X]: integer;
|
|
// var i, X, j: integer; -> var i, [X, ]j: integer;
|
|
// var X, i: integer; -> var [X, ]i: integer;
|
|
if DeleteStartPos<1 then
|
|
DeleteStartPos:=VarNode.StartPos;
|
|
MoveCursorToNodeStart(VarNode);
|
|
ReadNextAtom;
|
|
AtomIsIdentifier(true);
|
|
ReadNextAtom;
|
|
if CurPos.Flag=cafComma then begin
|
|
// there is a next variable in the same var definition
|
|
ReadNextAtom;
|
|
DeleteEndPos:=CurPos.StartPos;
|
|
end else if CurPos.Flag=cafColon then begin
|
|
// this is the last variable in the definition
|
|
DeleteEndPos:=CurPos.StartPos;
|
|
if (DeleteStartPos=VarNode.StartPos)
|
|
and (VarNode<>FirstVarNode) then begin
|
|
// there is a variable in front in the same definition, that is
|
|
// not deleted. Delete also the comma in front. Example:
|
|
// var i, X: integer; -> var i[, X]: integer;
|
|
MoveCursorToNodeStart(VarNode.PriorBrother);
|
|
ReadNextAtom; // prior identifier
|
|
ReadNextAtom; // comma
|
|
DeleteStartPos:=CurPos.StartPos;
|
|
end;
|
|
end;
|
|
// mark as removed
|
|
CurProcVar.RemovedFromOldProc:=true;
|
|
end else begin
|
|
// this variable is kept
|
|
DeleteCompleteDefinition:=false;
|
|
if DeleteStartPos>0 then begin
|
|
// delete variables in front
|
|
{$IFDEF CTDebug}
|
|
DebugLn('DeleteLocalVariable Delete last vars: "',copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos),'"');
|
|
{$ENDIF}
|
|
if not SourceChangeCache.Replace(gtNone,gtNone,
|
|
DeleteStartPos,DeleteEndPos,'')
|
|
then
|
|
exit;
|
|
DeleteStartPos:=0;
|
|
DeleteEndPos:=0;
|
|
end;
|
|
end;
|
|
if VarNode=LastVarNode then break;
|
|
VarNode:=VarNode.NextBrother;
|
|
until VarNode=nil;
|
|
FrontGap:=gtNone;
|
|
if DeleteCompleteDefinition and (DeleteStartPos>0) then begin
|
|
// all variables of the definition should be deleted
|
|
// -> delete type declaration
|
|
DeleteEndPos:=FindLineEndOrCodeAfterPosition(LastVarNode.EndPos);
|
|
if VarSectionIsEmpty then begin
|
|
// all variables of the 'var' section are deleted
|
|
// -> delete var section
|
|
DeleteStartPos:=FirstVarNode.Parent.StartPos;
|
|
end else if FirstVarNode.PriorBrother=nil then begin
|
|
// keep a space between 'var' and the next identifier
|
|
FrontGap:=gtSpace;
|
|
end;
|
|
DeleteStartPos:=FindLineEndOrCodeInFrontOfPosition(DeleteStartPos,true);
|
|
end;
|
|
if DeleteStartPos>0 then begin
|
|
{$IFDEF CTDebug}
|
|
DebugLn('DeleteLocalVariable Delete Rest: "',copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos),'"');
|
|
{$ENDIF}
|
|
if not SourceChangeCache.Replace(FrontGap,gtNone,
|
|
DeleteStartPos,DeleteEndPos,'')
|
|
then
|
|
exit;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function DeleteMovedLocalVariables: boolean;
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
begin
|
|
Result:=false;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.DeleteMovedLocalVariables A');
|
|
{$ENDIF}
|
|
// gather all variables, that are used in the selection, but not in the
|
|
// rest of the old proc. These are local variables, that are moved to the
|
|
// new proc.
|
|
if (VarTree<>nil) then begin
|
|
AVLNode:=VarTree.FindLowest;
|
|
while AVLNode<>nil do begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.DeleteMovedLocalVariables B ',GetIdentifier(@Src[ProcVar.Node.StartPos]),
|
|
' ReadInSelection=',dbgs(ProcVar.ReadInSelection),
|
|
' WriteInSelection=',dbgs(ProcVar.WriteInSelection),
|
|
' UsedInNonSelection=',dbgs(ProcVar.UsedInNonSelection),
|
|
' ReadAfterSelection=',dbgs(ProcVar.ReadAfterSelection),
|
|
'');
|
|
{$ENDIF}
|
|
if ProcVar.UsedInSelection and (not ProcVar.UsedInNonSelection) then
|
|
begin
|
|
if not DeleteLocalVariable(ProcVar) then exit;
|
|
end;
|
|
AVLNode:=VarTree.FindSuccessor(AVLNode);
|
|
end;
|
|
end;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('DeleteMovedLocalVariables END ');
|
|
{$ENDIF}
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateProcNameParts(out ProcClassName: string;
|
|
out ProcClassNode: TCodeTreeNode): boolean;
|
|
begin
|
|
Result:=false;
|
|
ProcClassName:='';
|
|
ProcClassNode:=nil;
|
|
if ProcType in [eptPrivateMethod,eptProtectedMethod,eptPublicMethod,
|
|
eptPublishedMethod] then
|
|
begin
|
|
{$IFDEF CTDebug}
|
|
DebugLn('CreateProcNameParts A searching class name ..');
|
|
{$ENDIF}
|
|
ProcClassName:=ExtractClassNameOfProcNode(ProcNode);
|
|
if ProcClassName='' then exit;
|
|
ProcClassNode:=FindClassNodeInUnit(ProcClassName,
|
|
true,false,false,true);
|
|
if ProcClassNode=nil then exit;
|
|
ProcClassName:=ExtractClassName(ProcClassNode,false);
|
|
end;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('CreateProcNameParts END ProcClassName="',ProcClassName,'"');
|
|
{$ENDIF}
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateProcParamList(
|
|
out CompleteParamListCode, // including modifiers, brackets and result type
|
|
BaseParamListCode: string // without modifiers and result type
|
|
): boolean;
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
ParamName: String;
|
|
ParamTypeCode: String;
|
|
ParamSpecifier: String;
|
|
ResultType: String;
|
|
begin
|
|
Result:=false;
|
|
CompleteParamListCode:='';
|
|
BaseParamListCode:='';
|
|
// gather all variables, that are used in the selection and in the rest of
|
|
// the old proc. These are the parameters for the new proc.
|
|
if (VarTree<>nil) and (ProcType<>eptSubProcedure) then begin
|
|
AVLNode:=VarTree.FindLowest;
|
|
while AVLNode<>nil do begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcParamList B ',GetIdentifier(@Src[ProcVar.Node.StartPos]),
|
|
' ReadInSelection=',dbgs(ProcVar.ReadInSelection),
|
|
' WriteInSelection=',dbgs(ProcVar.WriteInSelection),
|
|
' UsedInNonSelection=',dbgs(ProcVar.UsedInNonSelection),
|
|
' ReadAfterSelection=',dbgs(ProcVar.ReadAfterSelection),
|
|
'');
|
|
{$ENDIF}
|
|
if ProcVar.UsedInSelection and ProcVar.UsedInNonSelection
|
|
and (ProcVar.Node<>ResultNode) then begin
|
|
// extract identifier and type
|
|
if CompleteParamListCode<>'' then
|
|
CompleteParamListCode:=CompleteParamListCode+';';
|
|
if BaseParamListCode<>'' then
|
|
BaseParamListCode:=BaseParamListCode+';';
|
|
ParamName:=GetIdentifier(@Src[ProcVar.Node.StartPos]);
|
|
ParamTypeCode:=ExtractDefinitionNodeType(ProcVar.Node);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcParamList C ParamName="',ParamName,'" ParamType="',ParamTypeCode,'"');
|
|
{$ENDIF}
|
|
// ToDo: ParamSpecifier 'var ' and none
|
|
ParamSpecifier:='const ';
|
|
if ProcVar.ReadAfterSelection then
|
|
ParamSpecifier:='var ';
|
|
CompleteParamListCode:=CompleteParamListCode
|
|
+ParamSpecifier+ParamName+':'+ParamTypeCode;
|
|
BaseParamListCode:=BaseParamListCode+':'+ParamTypeCode;
|
|
end;
|
|
AVLNode:=VarTree.FindSuccessor(AVLNode);
|
|
end;
|
|
end;
|
|
if CompleteParamListCode<>'' then begin
|
|
CompleteParamListCode:='('+CompleteParamListCode+')';
|
|
BaseParamListCode:='('+BaseParamListCode+')';
|
|
end;
|
|
if ResultNode<>nil then begin
|
|
ResultType:=ExtractDefinitionNodeType(ResultNode);
|
|
CompleteParamListCode:=CompleteParamListCode+':'+ResultType;
|
|
end;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('CreateProcParamList END CompleteParamListCode="',CompleteParamListCode,'"');
|
|
{$ENDIF}
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateProcVarSection(out VarSectionCode: string): boolean;
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
VariableName: String;
|
|
VariableTypeCode: String;
|
|
VarTypeNode: TCodeTreeNode;
|
|
TypeDefEndPos: Integer;
|
|
begin
|
|
Result:=false;
|
|
VarSectionCode:='';
|
|
// gather all variables, that are used in the selection, but not in the
|
|
// rest of the old proc. These are the local variables of the new proc.
|
|
if (VarTree<>nil) then begin
|
|
AVLNode:=VarTree.FindLowest;
|
|
while AVLNode<>nil do begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcVarSection B ',GetIdentifier(@Src[ProcVar.Node.StartPos]),
|
|
' ReadInSelection=',dbgs(ProcVar.ReadInSelection),
|
|
' WriteInSelection=',dbgs(ProcVar.WriteInSelection),
|
|
' UsedInNonSelection=',dbgs(ProcVar.UsedInNonSelection),
|
|
' ReadAfterSelection=',dbgs(ProcVar.ReadAfterSelection),'');
|
|
{$ENDIF}
|
|
if ProcVar.UsedInSelection
|
|
and ((not ProcVar.UsedInNonSelection) or (ProcVar.Node=ResultNode)) then
|
|
begin
|
|
// extract identifier and type
|
|
if VarSectionCode='' then
|
|
VarSectionCode:='var'
|
|
+SourceChangeCache.BeautifyCodeOptions.LineEnd;
|
|
VarSectionCode:=VarSectionCode
|
|
+GetIndentStr(SourceChangeCache.BeautifyCodeOptions.Indent);
|
|
VariableName:=GetIdentifier(@Src[ProcVar.Node.StartPos]);
|
|
VarTypeNode:=FindTypeNodeOfDefinition(ProcVar.Node);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcVarSection VarTypeNode=',copy(Src,VarTypeNode.StartPos,VarTypeNode.EndPos-VarTypeNode.StartPos));
|
|
{$ENDIF}
|
|
TypeDefEndPos:=FindLineEndOrCodeAfterPosition(VarTypeNode.EndPos);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcVarSection PlusComment=',copy(Src,VarTypeNode.StartPos,TypeDefEndPos-VarTypeNode.StartPos));
|
|
{$ENDIF}
|
|
VariableTypeCode:=copy(Src,VarTypeNode.StartPos,
|
|
TypeDefEndPos-VarTypeNode.StartPos);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcVarSection C VariableName="',VariableName,'" VariableType="',VariableTypeCode,'"');
|
|
{$ENDIF}
|
|
VarSectionCode:=VarSectionCode+VariableName+':'+VariableTypeCode
|
|
+SourceChangeCache.BeautifyCodeOptions.LineEnd;
|
|
end;
|
|
AVLNode:=VarTree.FindSuccessor(AVLNode);
|
|
end;
|
|
end;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcVarSection END VarSectionCode="',VarSectionCode,'"');
|
|
{$ENDIF}
|
|
VarSectionCode:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
|
|
VarSectionCode,0);
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateProcBeginEndBlock(out BeginEndCode: string): boolean;
|
|
var
|
|
DirtyStartPos, DirtyEndPos: integer;
|
|
le, s: String;
|
|
Indent: Integer;
|
|
DirtySelection: String;
|
|
begin
|
|
Result:=false;
|
|
BeginEndCode:='';
|
|
le:=SourceChangeCache.BeautifyCodeOptions.LineEnd;
|
|
// extract dirty source, so that compiler directives are moved too
|
|
StartPos.Code.LineColToPosition(StartPos.Y,StartPos.X,DirtyStartPos);
|
|
StartPos.Code.LineColToPosition(EndPos.Y,EndPos.X,DirtyEndPos);
|
|
DirtySelection:=copy(StartPos.Code.Source,
|
|
DirtyStartPos,DirtyEndPos-DirtyStartPos);
|
|
// append line end
|
|
if (DirtySelection<>'')
|
|
and (not (DirtySelection[length(DirtySelection)] in [#10,#13])) then
|
|
DirtySelection:=DirtySelection+le;
|
|
// trim empty lines at start and end
|
|
DirtySelection:=TrimLineEnds(DirtySelection,true,true);
|
|
// adjust indent
|
|
Indent:=GetBlockMinIndent(DirtySelection,1,length(DirtySelection));
|
|
IndentText(DirtySelection,
|
|
SourceChangeCache.BeautifyCodeOptions.Indent-Indent,
|
|
SourceChangeCache.BeautifyCodeOptions.TabWidth,
|
|
s);
|
|
DirtySelection:=s;
|
|
if ResultNode<>nil then begin
|
|
DirtySelection:=DirtySelection
|
|
+GetIndentStr(SourceChangeCache.BeautifyCodeOptions.Indent)
|
|
+'Result:='+GetIdentifier(@Src[ResultNode.StartPos])+';'+le;
|
|
end;
|
|
// create Begin..End block
|
|
BeginEndCode:='begin'+le
|
|
+DirtySelection
|
|
+'end;';
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.CreateProcBeginEndBlock END BeginEndCode="',BeginEndCode,'"');
|
|
{$ENDIF}
|
|
Result:=true;
|
|
end;
|
|
|
|
function FindInsertPositionForProcBody(
|
|
out InsertPos, Indent: integer): boolean;
|
|
var
|
|
BeginNode: TCodeTreeNode;
|
|
ANode: TCodeTreeNode;
|
|
InsertNode: TCodeTreeNode;
|
|
begin
|
|
Result:=false;
|
|
case ProcType of
|
|
|
|
eptSubProcedure:
|
|
begin
|
|
BeginNode:=ProcNode.LastChild;
|
|
while BeginNode.Desc<>ctnBeginBlock do
|
|
BeginNode:=BeginNode.PriorBrother;
|
|
InsertPos:=BeginNode.StartPos;
|
|
Indent:=GetLineIndent(Src,InsertPos)
|
|
+SourceChangeCache.BeautifyCodeOptions.Indent;
|
|
end;
|
|
|
|
eptSubProcedureSameLvl:
|
|
begin
|
|
// -> insert in front of old proc
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ProcNode.StartPos);
|
|
Indent:=GetLineIndent(Src,ProcNode.StartPos);
|
|
end;
|
|
|
|
eptProcedure,eptProcedureWithInterface:
|
|
begin
|
|
// insert in front of top level proc
|
|
InsertNode:=ProcNode;
|
|
ANode:=InsertNode;
|
|
while (ANode<>nil) do begin
|
|
if ANode.Desc=ctnProcedure then
|
|
InsertNode:=ANode;
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
if NodeIsMethodBody(InsertNode) then begin
|
|
// insert in front of all methods
|
|
while (InsertNode.PriorBrother<>nil)
|
|
and (InsertNode.PriorBrother.Desc=ctnProcedure)
|
|
and (NodeIsMethodBody(InsertNode)) do
|
|
InsertNode:=InsertNode.PriorBrother;
|
|
end;
|
|
// -> insert in front of top level proc
|
|
Indent:=GetLineIndent(Src,InsertNode.StartPos);
|
|
if InsertNode.PriorBrother<>nil then begin
|
|
InsertPos:=FindLineEndOrCodeAfterPosition(
|
|
InsertNode.PriorBrother.EndPos);
|
|
end else if InsertNode.Parent.Desc=ctnImplementation then begin
|
|
MoveCursorToNodeStart(InsertNode.Parent);
|
|
ReadNextAtom;
|
|
InsertPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
|
|
end else begin
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertNode.StartPos,true);
|
|
end;
|
|
end;
|
|
|
|
eptPublishedMethod,eptPrivateMethod,eptProtectedMethod,eptPublicMethod:
|
|
begin
|
|
// set default values
|
|
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ProcNode.StartPos);
|
|
Indent:=GetLineIndent(Src,ProcNode.StartPos);
|
|
end;
|
|
|
|
else
|
|
exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function FindInsertPositionForProcIntf(
|
|
out IntfInsertPos, IntfIndent: integer): boolean;
|
|
begin
|
|
Result:=false;
|
|
IntfInsertPos:=0;
|
|
IntfIndent:=0;
|
|
case ProcType of
|
|
|
|
eptProcedureWithInterface:
|
|
begin
|
|
FindInsertPositionForProcInterface(IntfIndent,IntfInsertPos,
|
|
SourceChangeCache);
|
|
end;
|
|
|
|
end;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
function NewProcAlreadyExists(const ProcClassName, BaseParamList: string;
|
|
InsertPos: integer): boolean;
|
|
var
|
|
ContextNode: TCodeTreeNode;
|
|
ConflictProcNode: TCodeTreeNode;
|
|
ProcHead: String;
|
|
begin
|
|
// find context at insert position
|
|
ContextNode:=FindDeepestNodeAtPos(InsertPos,true);
|
|
if (ContextNode.Parent<>nil) then
|
|
ContextNode:=ContextNode.FirstChild;
|
|
// search proc in context
|
|
if ProcClassName<>'' then
|
|
ProcHead:=ProcClassName+'.'
|
|
else
|
|
ProcHead:='';
|
|
ProcHead:=ProcHead+ProcName+BaseParamList;
|
|
ConflictProcNode:=FindProcNode(ContextNode,ProcHead,
|
|
ShortProcFormat+[phpIgnoreForwards]);
|
|
Result:=ConflictProcNode<>nil;
|
|
if Result then begin
|
|
RaiseException('New procedure "'+ProcName+'" exists already');
|
|
end;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('NewProcAlreadExists END ProcHead="',ProcHead,'" Found=',dbgs(Result));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function InsertProcIntf(IntfInsertPos, IntfIndent: integer;
|
|
const CompleteParamList, BaseParamList, ProcCode: string;
|
|
const ProcClassName: string; ProcClassNode: TCodeTreeNode): boolean;
|
|
var
|
|
ProcHeader: String;
|
|
FrontGap: TGapTyp;
|
|
AfterGap: TGapTyp;
|
|
InsertNode: TCodeTreeNode;
|
|
MethodDefinition: String;
|
|
CleanMethodDefinition: String;
|
|
NewClassPart: TNewClassPart;
|
|
Keyword: String;
|
|
begin
|
|
Result:=false;
|
|
if ResultNode=nil then
|
|
Keyword:='procedure'
|
|
else
|
|
Keyword:='function';
|
|
|
|
case ProcType of
|
|
|
|
eptProcedureWithInterface:
|
|
begin
|
|
ProcHeader:=Keyword+' '+ProcName+CompleteParamList+';';
|
|
ProcHeader:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
|
|
ProcHeader,IntfIndent);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InsertProcIntf END ProcHeader="',ProcHeader,'"');
|
|
{$ENDIF}
|
|
FrontGap:=gtEmptyLine;
|
|
AfterGap:=gtEmptyLine;
|
|
InsertNode:=FindDeepestNodeAtPos(IntfInsertPos,false);
|
|
if (InsertNode<>nil) then begin
|
|
if (InsertNode.Desc=ctnProcedure) then
|
|
AfterGap:=gtNewLine;
|
|
if (InsertNode.PriorBrother<>nil)
|
|
and (InsertNode.PriorBrother.Desc=ctnProcedure) then
|
|
FrontGap:=gtNewLine;
|
|
end;
|
|
if not SourceChangeCache.Replace(FrontGap,AfterGap,
|
|
IntfInsertPos,IntfInsertPos,ProcHeader)
|
|
then exit;
|
|
end;
|
|
|
|
eptPublishedMethod,eptPrivateMethod,eptProtectedMethod,eptPublicMethod:
|
|
begin
|
|
// initialize class for code completion
|
|
CodeCompleteClassNode:=ProcClassNode;
|
|
CodeCompleteSrcChgCache:=SourceChangeCache;
|
|
|
|
// insert new method to class
|
|
MethodDefinition:=Keyword+' '+ProcName+CompleteParamList+';';
|
|
CleanMethodDefinition:=Keyword+' '+ProcName+BaseParamList+';';
|
|
if ProcExistsInCodeCompleteClass(CleanMethodDefinition) then exit;
|
|
case ProcType of
|
|
eptPrivateMethod: NewClassPart:=ncpPrivateProcs;
|
|
eptProtectedMethod: NewClassPart:=ncpProtectedProcs;
|
|
eptPublicMethod: NewClassPart:=ncpPublicProcs;
|
|
else NewClassPart:=ncpPublishedProcs;
|
|
end;
|
|
AddClassInsertion(CleanMethodDefinition, MethodDefinition,
|
|
ProcName, NewClassPart, nil, ProcCode);
|
|
if not InsertAllNewClassParts then
|
|
RaiseException(ctsErrorDuringInsertingNewClassParts);
|
|
end;
|
|
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreateProcBody(const ProcClassName, ParamList,
|
|
VarSection, BeginEndCode: string; out ProcCode: string): boolean;
|
|
var
|
|
le: String;
|
|
ProcHeader: String;
|
|
begin
|
|
le:=SourceChangeCache.BeautifyCodeOptions.LineEnd;
|
|
if ResultNode=nil then
|
|
ProcHeader:='procedure '
|
|
else
|
|
ProcHeader:='function ';
|
|
if ProcClassName<>'' then
|
|
ProcHeader:=ProcHeader+ProcClassName+'.';
|
|
ProcHeader:=ProcHeader+ProcName+ParamList+';'+le;
|
|
ProcHeader:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
|
|
ProcHeader,0);
|
|
ProcCode:=ProcHeader+VarSection+BeginEndCode;
|
|
Result:=true;
|
|
end;
|
|
|
|
function InsertProcBody(InsertPos,Indent: integer;
|
|
const ProcCode: string): boolean;
|
|
var
|
|
TabWidth: Integer;
|
|
IndentedProcCode: string;
|
|
begin
|
|
Result:=false;
|
|
if ProcType in [eptPublishedMethod,eptPrivateMethod,eptProtectedMethod,
|
|
eptPublicMethod] then
|
|
begin
|
|
if not CreateMissingProcBodies then
|
|
RaiseException(ctsErrorDuringCreationOfNewProcBodies);
|
|
end else begin
|
|
TabWidth:=SourceChangeCache.BeautifyCodeOptions.TabWidth;
|
|
IndentText(ProcCode,Indent,TabWidth,IndentedProcCode);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.InsertProcBody END ProcCode="',ProcCode,'"');
|
|
{$ENDIF}
|
|
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
|
|
InsertPos,InsertPos,IndentedProcCode) then exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function CreatePathForNewProc(InsertPos: integer;
|
|
const ProcClassName, BaseParamList: string;
|
|
var NewProcPath: TStrings): boolean;
|
|
var
|
|
ContextNode: TCodeTreeNode;
|
|
ProcHead: String;
|
|
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
|
|
if ProcClassName<>'' then
|
|
ProcHead:=ProcClassName+'.'
|
|
else
|
|
ProcHead:='';
|
|
ProcHead:=ProcHead+ProcName+BaseParamList+';';
|
|
NewProcPath.Add(ProcHead);
|
|
Result:=true;
|
|
end;
|
|
|
|
function FindJumpPointToNewProc(SubProcPath: TStrings): boolean;
|
|
var
|
|
NewProcNode: TCodeTreeNode;
|
|
begin
|
|
Result:=false;
|
|
// reparse code and find jump point into new proc
|
|
BuildTree(false);
|
|
NewProcNode:=FindSubProcPath(SubProcPath,ShortProcFormat,true);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('FindJumpPointToNewProc A found=',dbgs(NewProcNode<>nil));
|
|
{$ENDIF}
|
|
if NewProcNode=nil then exit;
|
|
Result:=FindJumpPointInProcNode(NewProcNode,NewPos,NewTopLine);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('FindJumpPointToNewProc END ',NewProcNode.DescAsString,' ',dbgs(Result),' ',dbgs(NewPos.X),',',dbgs(NewPos.Y),' ',dbgs(NewTopLine));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
var
|
|
MethodPossible: Boolean;
|
|
SubProcSameLvlPossible: boolean;
|
|
ProcClassName, CompleteParamList, BaseParamList, VarSection,
|
|
BeginEndCode: string;
|
|
InsertPos, Indent: integer;
|
|
IntfInsertPos, IntfIndent: integer;
|
|
NewProcPath: TStrings;
|
|
ProcClassNode: TCodeTreeNode;
|
|
ProcCode: string;
|
|
begin
|
|
Result:=false;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('ExtractProc A ProcName="',ProcName,'" ProcType=',ExtractProcTypeNames[ProcType],' FunctionResultVariableStartPos=',FunctionResultVariableStartPos);
|
|
{$ENDIF}
|
|
if not InitExtractProc(StartPos,EndPos,MethodPossible,SubProcSameLvlPossible)
|
|
then exit;
|
|
if (not MethodPossible) and (ProcType in [eptPrivateMethod,eptProtectedMethod,
|
|
eptPublicMethod,eptPublishedMethod])
|
|
then
|
|
exit;
|
|
if (not SubProcSameLvlPossible) and (ProcType=eptSubProcedureSameLvl) then
|
|
exit;
|
|
CodeCompleteSrcChgCache:=SourceChangeCache;
|
|
|
|
VarTree:=CreateExtractProcVariableTree;
|
|
NewProcPath:=nil;
|
|
try
|
|
if not ScanNodesForVariables(StartPos,EndPos,BlockStartPos,BlockEndPos,
|
|
ProcNode,VarTree,IgnoreIdentifiers,nil) then exit;
|
|
if not FindFunctionResultNode then exit;
|
|
if not ReplaceSelectionWithCall then exit;
|
|
if not DeleteMovedLocalVariables then exit;
|
|
if not CreateProcNameParts(ProcClassName,ProcClassNode) then exit;
|
|
if not CreateProcParamList(CompleteParamList,BaseParamList) then exit;
|
|
if not CreateProcVarSection(VarSection) then exit;
|
|
if not CreateProcBeginEndBlock(BeginEndCode) then exit;
|
|
if not FindInsertPositionForProcIntf(IntfInsertPos,IntfIndent) then exit;
|
|
if not FindInsertPositionForProcBody(InsertPos,Indent) then exit;
|
|
if NewProcAlreadyExists(ProcClassName,BaseParamList,InsertPos) then exit;
|
|
if not CreateProcBody(ProcClassName,CompleteParamList,
|
|
VarSection,BeginEndCode,ProcCode) then exit;
|
|
if not InsertProcIntf(IntfInsertPos,IntfIndent,CompleteParamList,
|
|
BaseParamList,ProcCode,ProcClassName,ProcClassNode) then exit;
|
|
if not InsertProcBody(InsertPos,Indent,ProcCode) then exit;
|
|
if not CreatePathForNewProc(InsertPos,ProcClassName,BaseParamList,
|
|
NewProcPath) then exit;
|
|
if not SourceChangeCache.Apply then exit;
|
|
if not FindJumpPointToNewProc(NewProcPath) then exit;
|
|
finally
|
|
ClearExtractProcVariableTree(VarTree,true);
|
|
NewProcPath.Free;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TExtractProcTool.CalcMemSize(Stats: TCTMemStats);
|
|
begin
|
|
inherited CalcMemSize(Stats);
|
|
end;
|
|
|
|
function TExtractProcTool.ScanNodesForVariables(const StartPos,
|
|
EndPos: TCodeXYPosition; out BlockStartPos, BlockEndPos: integer;
|
|
out ProcNode: TCodeTreeNode;
|
|
VarTree: TAVLTree; // tree of TExtractedProcVariable
|
|
IgnoreIdentifiers: TAVLTree; // tree of PCodeXYPosition
|
|
MissingIdentifiers: TAVLTree// tree of PCodeXYPosition
|
|
): boolean;
|
|
type
|
|
TParameterType = (ptNone, ptConst, ptVar, ptOut, ptNoSpecifier);
|
|
|
|
procedure AddVariableToTree(VarNode: TCodeTreeNode; IsInSelection,
|
|
IsAfterSelection, IsChanged: boolean; ParameterType: TParameterType);
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
ProcVar: TExtractedProcVariable;
|
|
begin
|
|
{$IFDEF CTDebug}
|
|
DebugLn('AddVariableToTree A Ident=',GetIdentifier(@Src[VarNode.StartPos]),
|
|
' IsInSelection=',dbgs(IsInSelection),
|
|
' ParameterType=',ParameterTypeNames[ParameterType]);
|
|
{$ENDIF}
|
|
if VarTree=nil then exit;
|
|
|
|
AVLNode:=VarTree.FindKey(VarNode,TListSortCompare(@CompareNodeWithExtractedProcVariable));
|
|
if AVLNode<>nil then begin
|
|
ProcVar:=TExtractedProcVariable(AVLNode.Data);
|
|
end else begin
|
|
ProcVar:=TExtractedProcVariable.Create;
|
|
ProcVar.Node:=VarNode;
|
|
ProcVar.Tool:=Self;
|
|
end;
|
|
ProcVar.ReadInSelection:=ProcVar.ReadInSelection or IsInSelection;
|
|
ProcVar.WriteInSelection:=ProcVar.WriteInSelection
|
|
or (IsInSelection and IsChanged);
|
|
ProcVar.UsedInNonSelection:=ProcVar.UsedInNonSelection
|
|
or (not IsInSelection) or (ParameterType<>ptNone);
|
|
if (not ProcVar.ReadAfterSelectionValid) then begin
|
|
// a) variable is a var or out parameter
|
|
// => the variable value IS needed after the extracted proc
|
|
// b) just after the selection the variable is read
|
|
// => the variable value IS needed after the extracted proc
|
|
// c) just after the selection the variable is written
|
|
// => the variable value IS NOT needed after the extracted proc
|
|
if (ParameterType in [ptOut,ptVar]) then begin
|
|
ProcVar.ReadAfterSelectionValid:=true;
|
|
ProcVar.ReadAfterSelection:=true;
|
|
end else if (not IsInSelection) and IsAfterSelection then begin
|
|
ProcVar.ReadAfterSelectionValid:=true;
|
|
ProcVar.ReadAfterSelection:=not IsChanged;
|
|
end;
|
|
end;
|
|
if AVLNode=nil then begin
|
|
if ParameterType<>ptNone then
|
|
ProcVar.VarType:=epvtParameter
|
|
else
|
|
ProcVar.VarType:=epvtLocalVar;
|
|
VarTree.Add(ProcVar);
|
|
end;
|
|
end;
|
|
|
|
function VariableIsChanged(VarStartPos: integer): boolean;
|
|
begin
|
|
Result:=false;
|
|
MoveCursorToCleanPos(VarStartPos);
|
|
// read identifier
|
|
ReadNextAtom;
|
|
if CurPos.Flag in [cafRoundBracketOpen] then
|
|
ReadTilBracketClose(true);
|
|
// read next atom
|
|
ReadNextAtom;
|
|
if AtomIs(':=') or AtomIs('+=') or AtomIs('-=') or AtomIs('*=')
|
|
or AtomIs('/=') then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
function CheckVariableAtCursor: boolean;
|
|
// find declaration of identifier at cursor and add to variable tree
|
|
var
|
|
Params: TFindDeclarationParams;
|
|
VarStartPos: Integer;
|
|
VarNode: TCodeTreeNode;
|
|
IsInSelection: Boolean;
|
|
ClosestProcNode: TCodeTreeNode;
|
|
IsParameter: boolean;
|
|
IsChanged: Boolean;
|
|
IsAfterSelection: Boolean;
|
|
ParameterType: TParameterType;
|
|
NewCodePos: TCodeXYPosition;
|
|
begin
|
|
Result:=false;
|
|
// find start of variable
|
|
VarStartPos:=FindStartOfTerm(CurPos.StartPos,false);
|
|
if (IgnoreIdentifiers<>nil) then begin
|
|
if not CleanPosToCaret(VarStartPos,NewCodePos) then exit;
|
|
if IgnoreIdentifiers.Find(@NewCodePos)<>nil then exit(true);
|
|
end;
|
|
|
|
IsInSelection:=(VarStartPos>=BlockStartPos) and (VarStartPos<BlockEndPos);
|
|
IsAfterSelection:=(VarStartPos>=BlockEndPos);
|
|
MoveCursorToCleanPos(VarStartPos);
|
|
Params:=TFindDeclarationParams.Create;
|
|
try
|
|
// find declaration
|
|
Params.ContextNode:=FindDeepestNodeAtPos(VarStartPos,true);
|
|
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound,
|
|
fdfTopLvlResolving,fdfSearchInAncestors];
|
|
Params.SetIdentifier(Self,@Src[VarStartPos],@CheckSrcIdentifier);
|
|
{$IFDEF CTDebug}
|
|
DebugLn('AddVariableAtCursor Searching ',GetIdentifier(Params.Identifier));
|
|
{$ENDIF}
|
|
try
|
|
FindDeclarationOfIdentAtParam(Params);
|
|
except
|
|
on E: ECodeToolError do begin
|
|
{$IFDEF CTDebug}
|
|
DebugLn('AddVariableAtCursor identifier not found ',GetIdentifier(@Src[VarStartPos]));
|
|
{$ENDIF}
|
|
if MissingIdentifiers=nil then
|
|
raise;
|
|
// collect missing identifiers
|
|
if not CleanPosToCaret(VarStartPos,NewCodePos) then exit;
|
|
AddCodePosition(MissingIdentifiers,NewCodePos);
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
end;
|
|
// check if declaration is local variable
|
|
if (Params.NewCodeTool=Self) and (Params.NewNode<>nil) then begin
|
|
VarNode:=Params.NewNode;
|
|
if (VarNode.Desc=ctnVarDefinition)
|
|
and (VarNode.HasAsParent(ProcNode)) then begin
|
|
// Now we know: VarNode is a variable defined in the main proc
|
|
// or one of its sub procs
|
|
ClosestProcNode:=VarNode.GetNodeOfType(ctnProcedure);
|
|
if ClosestProcNode=ProcNode then begin
|
|
// VarNode is a variable defined by the main proc
|
|
IsParameter:=VarNode.GetNodeOfType(ctnProcedureHead)<>nil;
|
|
ParameterType:=ptNone;
|
|
if IsParameter then begin
|
|
MoveCursorToParameterSpecifier(VarNode);
|
|
if UpAtomIs('CONST') then
|
|
ParameterType:=ptConst
|
|
else if UpAtomIs('VAR') then
|
|
ParameterType:=ptVar
|
|
else if UpAtomIs('OUT') then
|
|
ParameterType:=ptOut
|
|
else
|
|
ParameterType:=ptNoSpecifier;
|
|
end;
|
|
IsChanged:=VariableIsChanged(VarStartPos);
|
|
AddVariableToTree(VarNode,IsInSelection,IsAfterSelection,IsChanged,
|
|
ParameterType);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
Params.Free;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function ScanSourceForVariables(CleanStartPos, CleanEndPos: integer): boolean;
|
|
// scan part of the source for variables
|
|
var
|
|
LastAtomType: TCommonAtomFlag;
|
|
OldCursor: Integer;
|
|
begin
|
|
Result:=false;
|
|
{$IFDEF CTDebug}
|
|
DebugLn('TExtractProcTool.ScanSourceForVariables A "',copy(Src,CleanStartPos,CleanEndPos-CleanStartPos),'"');
|
|
{$ENDIF}
|
|
MoveCursorToNearestAtom(CleanStartPos);
|
|
while CurPos.StartPos<CleanEndPos do begin
|
|
LastAtomType:=CurPos.Flag;
|
|
ReadNextAtom;
|
|
if AtomIsIdentifier(false) and (LastAtomType<>cafPoint) then begin
|
|
// this could be the start of a variable -> check
|
|
{$IFDEF CTDebug}
|
|
DebugLn('ScanSourceForVariables B Identifier=',GetAtom);
|
|
{$ENDIF}
|
|
OldCursor:=CurPos.StartPos;
|
|
if not CheckVariableAtCursor then exit;
|
|
// restore cursor
|
|
MoveCursorToCleanPos(OldCursor);
|
|
ReadNextAtom;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function ScanNodesForVariablesRecursive(StartNode: TCodeTreeNode): boolean;
|
|
// scan recursively all statements for variables
|
|
var
|
|
ChildNode: TCodeTreeNode;
|
|
begin
|
|
{$IFDEF CTDebug}
|
|
DebugLn('ScanNodesForVariablesRecursive A Node=',StartNode.DescAsString);
|
|
{$ENDIF}
|
|
Result:=false;
|
|
ChildNode:=StartNode.FirstChild;
|
|
while ChildNode<>nil do begin
|
|
if (ChildNode.Desc in [ctnBeginBlock,ctnAsmBlock])
|
|
and (ChildNode.Parent.Desc=ctnProcedure) then begin
|
|
if not ScanSourceForVariables(ChildNode.StartPos,ChildNode.EndPos) then
|
|
exit;
|
|
end;
|
|
if not ScanNodesForVariablesRecursive(ChildNode) then exit;
|
|
ChildNode:=ChildNode.NextBrother;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
begin
|
|
Result:=false;
|
|
if CaretToCleanPos(StartPos,BlockStartPos)<>0 then exit;
|
|
if CaretToCleanPos(EndPos,BlockEndPos)<>0 then exit;
|
|
BuildSubTree(BlockStartPos);
|
|
ProcNode:=FindDeepestNodeAtPos(BlockStartPos,true).GetNodeOfType(ctnProcedure);
|
|
|
|
ActivateGlobalWriteLock;
|
|
try
|
|
if not ScanNodesForVariablesRecursive(ProcNode) then exit;
|
|
finally
|
|
DeactivateGlobalWriteLock;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
end.
|
|
|