lazarus/components/codetools/stdcodetools.pas

7258 lines
252 KiB
ObjectPascal

{
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
TStandardCodeTool enhances TIdentCompletionTool with many standard code
editing functions for the following categories:
- source name
- uses sections
- lazarus resources
- Application.CreateForm statements
- published variables
- resource strings
- compiler and IDE directives
- code exploring
- code blocks
}
unit StdCodeTools;
{$ifdef FPC}{$mode objfpc}{$endif}{$H+}
interface
{$I codetools.inc}
{ $DEFINE VerboseGetStringConstBounds}
{ $DEFINE VerboseCompleteBlock}
{ $DEFINE VerboseCheckLFM}
{ $DEFINE VerboseFindUnusedUnits}
uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
Classes, SysUtils, TypInfo, Laz_AVL_Tree,
// Codetools
CodeToolsStrConsts, FileProcs, CodeTree, CodeAtom,
FindDeclarationTool, IdentCompletionTool, PascalReaderTool, PascalParserTool,
ExprEval, KeywordFuncLists, BasicCodeTools, LinkScanner,
CodeCache, LFMTrees, SourceChanger, CustomCodeTool, CodeToolsStructs,
// LazUtils
LazFileUtils, LazFileCache, LazUTF8, AvgLvlTree;
type
TStandardCodeTool = class;
TInsertStatementPosDescription = class
public
InsertPos: integer;
Indent: integer;
CodeXYPos: TCodeXYPosition;
FrontGap, AfterGap: TGapTyp;
Description: string;
end;
TUsesSection = (usMain, usImplementation);
TAddUsesFlag = (
aufLast,
aufNotCheckSpecialUnit
);
TAddUsesFlags = set of TAddUsesFlag;
TOnFindDefinePropertyForContext = procedure(Sender: TObject;
const ClassContext, AncestorClassContext: TFindContext;
LFMNode: TLFMTreeNode;
const IdentName: string; var IsDefined: boolean) of object;
TOnIDEDirectiveFilter = function(Tool: TStandardCodeTool;
StartPos, EndPos: integer): boolean of object; // true = use
{ TStandardCodeTool }
TStandardCodeTool = class(TIdentCompletionTool)
private
function ReadTilGuessedUnclosedBlock(MinCleanPos: integer;
ReadOnlyOneBlock: boolean): boolean;
function ReadForwardTilAnyBracketClose: boolean;
function ReadBackwardTilAnyBracketClose: boolean;
function FindApplicationStatement(const APropertyUpCase: string;
out StartPos, ConstStartPos, EndPos: integer): boolean;
function SetApplicationStatement(const APropertyName, NewCode: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveApplicationStatement(const APropertyUpCase: string;
SourceChangeCache: TSourceChangeCache): boolean;
public
// explore the code
function Explore(WithStatements: boolean; Range: TLinkScannerRange): boolean;
function Explore(WithStatements: boolean;
OnlyInterface: boolean = false): boolean;
// source name e.g. 'unit UnitName;'
function GetCachedSourceName: string;
function RenameSource(const NewName: string;
SourceChangeCache: TSourceChangeCache): boolean;
// uses sections
function RenameUsedUnit(const OldUnitName, NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache): boolean;
function ReplaceUsedUnits(UnitNamePairs: TStringToStringTree; // ToDo: dotted
SourceChangeCache: TSourceChangeCache): boolean;
function AddUnitToUsesSection(UsesNode: TCodeTreeNode;
const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
function AddUnitToSpecificUsesSection(UsesSection: TUsesSection;
const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
function AddUnitToMainUsesSection(const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
function AddUnitToImplementationUsesSection(const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
function UnitExistsInUsesSection(UsesSection: TUsesSection;
const AnUnitName: string): boolean;
function UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
const AnUnitName: string): boolean;
function RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
const AnUnitName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveUnitFromAllUsesSections(const AnUnitName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function FixUsedUnitCase(SourceChangeCache: TSourceChangeCache): boolean;
function FixUsedUnitCaseInUsesSection(UsesNode: TCodeTreeNode;
SourceChangeCache: TSourceChangeCache): boolean;
function FindUsedUnitNames(var MainUsesSection,
ImplementationUsesSection: TStrings): boolean;
function FindUsedUnitNames(var List: TStringToStringTree): boolean;
function FindUsedUnitFiles(var MainUsesSection: TStrings): boolean;
function FindUsedUnitFiles(var MainUsesSection,
ImplementationUsesSection: TStrings): boolean;
function FindDelphiProjectUnits(out FoundInUnits, MissingInUnits,
NormalUnits: TStrings;
UseContainsSection: boolean = false;
IgnoreNormalUnits: boolean = false): boolean;
function UsesSectionToFilenames(UsesNode: TCodeTreeNode): TStrings;
function UsesSectionToUnitnames(UsesNode: TCodeTreeNode): TStrings;
function FindMissingUnits(var MissingUnits: TStrings; FixCase: boolean;
SearchImplementation: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function CommentUnitsInUsesSection(MissingUnits: TStrings;
SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
function CommentUnitsInUsesSections(MissingUnits: TStrings;
SourceChangeCache: TSourceChangeCache): boolean;
function FindUnusedUnits(Units: TStrings): boolean;
// lazarus resources
function FindNextIncludeInInitialization(
var LinkIndex: integer): TCodeBuffer;
function FindLazarusResourceInBuffer(ResourceCode: TCodeBuffer;
const ResourceName: string): TAtomPosition;
function FindLazarusResource(const ResourceName: string): TAtomPosition;
function AddLazarusResource(ResourceCode: TCodeBuffer;
const ResourceName, ResourceData: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveLazarusResource(ResourceCode: TCodeBuffer;
const ResourceName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RenameInclude(LinkIndex: integer; const NewFilename: string;
KeepPath: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
const OnFindDefineProperty: TOnFindDefinePropertyForContext;
RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
ObjectsMustExist: boolean): boolean;
// Application.Createform statements
function FindCreateFormStatement(StartPos: integer;
const AClassName, AVarName: string;
out Position: TAtomPosition): integer; // 0=found, -1=not found, 1=found, but wrong classname
function AddCreateFormStatement(const AClassName, AVarName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveCreateFormStatement(const AVarName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function ChangeCreateFormStatement(StartPos: integer;
const OldClassName, OldVarName: string;
const NewClassName, NewVarName: string;
OnlyIfExists: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function ListAllCreateFormStatements: TStrings;
function SetAllCreateFromStatements(List: TStrings;
SourceChangeCache: TSourceChangeCache): boolean;
// Application.Title:=<string const> statements
function FindApplicationTitleStatement(out StartPos, StringConstStartPos,
EndPos: integer): boolean;
function GetApplicationTitleStatement(StringConstStartPos, EndPos: integer;
var Title: string): boolean;
function SetApplicationTitleStatement(const NewTitle: string;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveApplicationTitleStatement(
SourceChangeCache: TSourceChangeCache): boolean;
// Application.Scaled:=<boolean const> statements
function FindApplicationScaledStatement(out StartPos, BooleanConstStartPos,
EndPos: integer): boolean;
function GetApplicationScaledStatement(BooleanConstStartPos, EndPos: integer;
var AScaled: boolean): boolean;
function SetApplicationScaledStatement(const NewScaled: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function RemoveApplicationScaledStatement(
SourceChangeCache: TSourceChangeCache): boolean;
// forms
function RenameForm(const OldFormName, OldFormClassName: string;
const NewFormName, NewFormClassName: string;
SourceChangeCache: TSourceChangeCache): boolean;
function FindFormAncestor(const AClassName: string;
out AncestorClassName: string): boolean;
// published variables
function FindPublishedVariable(const AClassName, AVarName: string;
ExceptionOnClassNotFound: boolean): TCodeTreeNode;
function GatherPublishedVarTypes(const AClassName: string;
out VarNameToType: TStringToStringTree): boolean;
function AddPublishedVariable(const AClassName,VarName, VarType: string;
SourceChangeCache: TSourceChangeCache): boolean; virtual;
function RemovePublishedVariable(const AClassName, AVarName: string;
ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function RenamePublishedVariable(const AClassName,
AOldVarName: string; const NewVarName, VarType: shortstring;
ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function GatherPublishedClassElements(const TheClassName: string;
ExceptionOnClassNotFound, WithVariables, WithMethods,
WithProperties, WithAncestors: boolean;
out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
function RetypeClassVariables(const AClassName: string;
ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache;
SearchImplementationToo: boolean = false): boolean;
function FindDanglingComponentEvents(const TheClassName: string;
RootComponent: TComponent; ExceptionOnClassNotFound,
SearchInAncestors: boolean;
out ListOfPInstancePropInfo: TFPList;
const OverrideGetMethodName: TOnGetMethodname = nil): boolean;
// variables, constants, types
function RemoveIdentifierDefinition(const CursorPos: TCodeXYPosition;
SourceChangeCache: TSourceChangeCache): boolean;
function InsertStatements(InsertPos: TInsertStatementPosDescription;
Statements: string; SourceChangeCache: TSourceChangeCache): boolean;
function InsertStatements(CleanPos: integer;
Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
SourceChangeCache: TSourceChangeCache): boolean;
// blocks (e.g. begin..end)
function FindBlockCounterPart(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IncludeCloseToken: Boolean = false): boolean;
function FindBlockStart(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
SkipStart: boolean = true): boolean;
function GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindBlockCleanBounds(const CursorPos: TCodeXYPosition;
out BlockCleanStart, BlockCleanEnd: integer): boolean;
function CompleteBlock(const CursorPos: TCodeXYPosition;
SourceChangeCache: TSourceChangeCache;
OnlyIfCursorBlockIndented: boolean;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
// compiler directives
{$IFDEF GuessMisplacedIfdef}
function GuessMisplacedIfdefEndif(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
{$ENDIF}
function FindEnclosingIncludeDirective(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindModeDirective(DoBuildTree: boolean;
out ACleanPos: integer): boolean;
function FindResourceDirective(DoBuildTree: boolean;
var ACleanPos: integer; const Filename: string = ''): boolean;
function FindResourceDirective(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
const Filename: string = ''): boolean;
function AddResourceDirective(const Filename: string;
SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
): boolean;
function FindIncludeDirective(DoBuildTree: boolean;
var ACleanPos: integer; const Filename: string = ''): boolean;
function FindIncludeDirective(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
const Filename: string = ''): boolean;
function AddIncludeDirectiveForInit(const Filename: string;
SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
): boolean;
function AddUnitWarnDirective(WarnID, Comment: string; TurnOn: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function FixIncludeFilenames(Code: TCodeBuffer;
SourceChangeCache: TSourceChangeCache;
out FoundIncludeFiles: TStrings;
var MissingIncludeFilesCodeXYPos: TFPList): boolean;
// search & replace
function ReplaceWords(IdentList: TStrings; ChangeStrings: boolean;
SourceChangeCache: TSourceChangeCache;
SkipPointWords: boolean = false): boolean;
function FindNearestIdentifierNode(const CursorPos: TCodeXYPosition;
IdentTree: TAVLTree): TAVLTreeNode;
function ReplaceWord(const OldWord, NewWord: string; ChangeStrings: boolean;
SourceChangeCache: TSourceChangeCache;
SkipPointWords: boolean = false): boolean;
// comments
function CommentCode(const StartPos, EndPos: integer;
SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;
// expressions
function GetStringConstBounds(const CursorPos: TCodeXYPosition;
out StartPos, EndPos: TCodeXYPosition;
ResolveComments: boolean): boolean;
function GetStringConstAsFormatString(StartPos, EndPos: integer;
out FormatStringConstant, FormatParameters: string;
out StartInStringConst, EndInStringConst: boolean): boolean;
function GetStringConstAsFormatString(StartPos, EndPos: integer;
out FormatStringConstant, FormatParameters: string): boolean;
function ExtractOperand(const CursorPos: TCodeXYPosition;
out Operand: string; WithPostTokens, WithAsOperator,
WithoutTrailingPoints: boolean): boolean;
// resource strings
function GatherResourceStringSections(const CursorPos: TCodeXYPosition;
PositionList: TCodeXYPositions): boolean;
function IdentifierExistsInResourceStringSection(
const CursorPos: TCodeXYPosition;
const ResStrIdentifier: string): boolean;
function GatherResourceStringsWithValue(const CursorPos: TCodeXYPosition;
const StringValue: string;
PositionList: TCodeXYPositions): boolean;
function GatherResourceStringIdents(const SectionPos: TCodeXYPosition;
var IdentTree: TAVLTree): boolean;
function FindNearestResourceString(const CursorPos,
SectionPos: TCodeXYPosition;
var NearestPos: TCodeXYPosition): boolean;
function AddResourceString(const SectionPos: TCodeXYPosition;
const NewIdentifier, NewValue: string;
InsertPolicy: TResourcestringInsertPolicy;
const NearestPos: TCodeXYPosition;
SourceChangeCache: TSourceChangeCache): boolean;
function CreateIdentifierFromStringConst(
const StartCursorPos, EndCursorPos: TCodeXYPosition;
out Identifier: string; MaxLen: integer): boolean;
function StringConstToFormatString(
const StartCursorPos, EndCursorPos: TCodeXYPosition;
out FormatStringConstant,FormatParameters: string;
out StartInStringConst, EndInStringConst: boolean): boolean;
// register procedure
function HasInterfaceRegisterProc: boolean;
// Delphi to Lazarus conversion
function ConvertDelphiToLazarusSource(AddLRSCode: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
// IDE % directives
function GetIDEDirectives(DirectiveList: TStrings;
const Filter: TOnIDEDirectiveFilter = nil): boolean;
function SetIDEDirectives(DirectiveList: TStrings;
SourceChangeCache: TSourceChangeCache;
const Filter: TOnIDEDirectiveFilter = nil): boolean;
// debugging
procedure CalcMemSize(Stats: TCTMemStats); override;
end;
implementation
type
TBlockKeyword = (bkwNone, bkwBegin, bkwAsm, bkwTry, bkwCase, bkwRepeat,
bkwRecord, bkwClass, bkwObject, bkwInterface,
bkwDispInterface, bkwEnd, bkwUntil, bkwFinally,
bkwExcept);
const
BlockKeywords: array[TBlockKeyword] of string = (
'(unknown)', 'BEGIN', 'ASM', 'TRY', 'CASE', 'REPEAT', 'RECORD', 'CLASS',
'OBJECT', 'INTERFACE', 'DISPINTERFACE', 'END', 'UNTIL', 'FINALLY',
'EXCEPT'
);
var
BlockKeywordFuncList: TKeyWordFunctionList;
procedure BuildBlockKeyWordFuncList;
var BlockWord: TBlockKeyword;
begin
if BlockKeywordFuncList=nil then begin
BlockKeywordFuncList:=TKeyWordFunctionList.Create('StdCodeTools.BlockKeywordFuncList');
for BlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
with BlockKeywordFuncList do
Add(BlockKeywords[BlockWord],{$ifdef FPC}@{$endif}AllwaysTrue);
end;
end;
{ TStandardCodeTool }
{-------------------------------------------------------------------------------
function TStandardCodeTool.GetCachedSourceName: string;
Params: none
Result: the source name (= e.g. the identifier behind 'program'/'unit' keyword)
This function does neither check if source needs reparsing, nor does it check
for errors in code. It simple checks if there is a first node, which is
typically the source type and name.
This function can therefore be used as a fast GetSourceName function.
-------------------------------------------------------------------------------}
function TStandardCodeTool.GetCachedSourceName: string;
begin
Result:=GetSourceName(false);
end;
function TStandardCodeTool.RenameSource(const NewName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
NamePos: TAtomPosition;
begin
Result:=false;
BuildTree(lsrSourceName);
//debugln(['TStandardCodeTool.RenameSource NewName=',NewName]);
if (not GetSourceNamePos(NamePos)) or (NamePos.StartPos<1) or (NewName='')
or (Length(NewName)>255) then exit;
//debugln(['TStandardCodeTool.RenameSource OldName="',dbgstr(copy(Src,NamePos.StartPos,NamePos.EndPos-NamePos.StartPos)),'"']);
SourceChangeCache.MainScanner:=Scanner;
SourceChangeCache.Replace(gtNone,gtNone,NamePos.StartPos,NamePos.EndPos,NewName);
if not SourceChangeCache.Apply then exit;
CachedSourceName:=NewName;
Result:=true;
end;
function TStandardCodeTool.RenameUsedUnit(const OldUnitName, NewUnitName,
NewUnitInFile: string; SourceChangeCache: TSourceChangeCache): boolean;
var
UnitPos, InPos: TAtomPosition;
NewUsesTerm: string;
begin
Result:=false;
if (not IsDottedIdentifier(OldUnitName))
or (not IsDottedIdentifier(NewUnitName)) then
exit;
if not FindUnitInAllUsesSections(OldUnitName,UnitPos,InPos) then begin
//debugln('TStandardCodeTool.RenameUsedUnit not found: ',OldUnitName,' ');
exit;
end;
SourceChangeCache.MainScanner:=Scanner;
if InPos.StartPos>0 then
UnitPos.EndPos:=InPos.EndPos;
// build use unit term
NewUsesTerm:=NewUnitName;
if NewUnitInFile<>'' then
NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+'''';
// Note: do not use beautifier, unit names are case sensitive
if ReplacementNeedsLineEnd(Src,UnitPos.StartPos,UnitPos.EndPos,
length(NewUsesTerm),SourceChangeCache.BeautifyCodeOptions.LineLength) then
begin
if not SourceChangeCache.Replace(gtNewLine,gtNone,
UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
end else begin
if not SourceChangeCache.Replace(gtSpace,gtNone,
UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
end;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.ReplaceUsedUnits(UnitNamePairs: TStringToStringTree;
SourceChangeCache: TSourceChangeCache): boolean;
var
ExistingUnits: TStringToStringTree;
procedure CleanNewUnits(const AnUnitName: string; var NewText: string);
var
StartPos: Integer;
EndPos: LongInt;
CommaBehind: LongInt;
CommaInFront: Integer;
NewUnitName: String;
begin
// remove all units, that already exists
StartPos:=1;
CommaInFront:=-1;
while StartPos<=length(NewText) do begin
EndPos:=StartPos;
while (EndPos<=length(NewText)) and (IsIdentChar[NewText[EndPos]]) do
inc(EndPos);
if EndPos<=StartPos then break;
NewUnitName:=copy(NewText,StartPos,EndPos-StartPos);
// set EndPos to start of next unit
CommaBehind:=-1;
while (EndPos<=length(NewText)) do begin
if NewText[EndPos]='''' then begin
inc(EndPos);
while (EndPos<=length(NewText)) and (NewText[EndPos]<>'''') do
inc(EndPos);
end else if NewText[EndPos]=',' then begin
CommaBehind:=EndPos;
while (EndPos<=length(NewText))
and (not IsIdentStartChar[NewText[EndPos]]) do
inc(EndPos);
break;
end;
inc(EndPos);
end;
if (SysUtils.CompareText(AnUnitName,NewUnitName)=0) then begin
// this is the old unit or
//DebugLn('Replace: keep old unit "',NewUnitName,'"');
end else if ExistingUnits.Contains(NewUnitName) then begin
// this unit already exists and should not be added
//DebugLn('Replace: already exists: "',NewUnitName,'"="',ExistingUnits[NewUnitName],'" CommaInFront=',dbgs(CommaInFront),' CommaBehind=',dbgs(CommaBehind));
if CommaBehind>0 then
System.Delete(NewText,StartPos,EndPos-StartPos)
else if CommaInFront>0 then
System.Delete(NewText,CommaInFront,EndPos-CommaInFront)
else
System.Delete(NewText,StartPos,EndPos-StartPos);
EndPos:=StartPos;
CommaBehind:=-1;
end else begin
// this unit does not exist yet
//DebugLn('Replace new unit with "',NewUnitName,'"');
end;
if CommaBehind>0 then
CommaInFront:=CommaBehind;
StartPos:=EndPos;
end;
end;
function Replace(UsesNode: TCodeTreeNode): boolean;
var
UnitNameAtom: TAtomPosition;
InAtom: TAtomPosition;
NewText: string;
CommaInFront: LongInt;
FromPos: LongInt;
ToPos: LongInt;
CommaBehind: Integer;
AnUnitName: String;
begin
if UsesNode=nil then exit(true);
MoveCursorToUsesStart(UsesNode);
CommaInFront:=-1;
repeat
// read next unit name
ReadNextUsedUnit(UnitNameAtom, InAtom);
if CurPos.Flag=cafComma then
CommaBehind:=CurPos.StartPos
else
CommaBehind:=-1;
AnUnitName:=GetAtom(UnitNameAtom);
if UnitNamePairs.Contains(AnUnitName) then begin
// replace
NewText:=UnitNamePairs[AnUnitName];
//DebugLn('Replace Unit="',AnUnitName,'" NewText="',NewText,'"');
CleanNewUnits(AnUnitName,NewText);
if NewText='' then begin
// comment unit
if CommaInFront>0 then begin
// example: uses a{, b};
FromPos:=CommaInFront;
ToPos:=UnitNameAtom.EndPos;
if InAtom.StartPos>0 then
ToPos:=InAtom.EndPos;
end else if CommaBehind>0 then begin
// example: uses {a,} b;
// uses {a,} {b};
FromPos:=UnitNameAtom.StartPos;
ToPos:=CommaBehind+1;
end else begin
// examples: uses {b};
FromPos:=UnitNameAtom.StartPos;
ToPos:=UnitNameAtom.EndPos;
if InAtom.StartPos>0 then
ToPos:=InAtom.EndPos;
end;
if not CommentCode(FromPos,ToPos,SourceChangeCache,false) then
exit(false);
end else begin
// replace
FromPos:=UnitNameAtom.StartPos;
ToPos:=UnitNameAtom.EndPos;
if InAtom.StartPos>0 then
ToPos:=InAtom.EndPos;
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewText)
then exit(false);
end;
end;
if CurPos.Flag=cafComma then begin
// read next unit name
CommaInFront:=CurPos.StartPos;
ReadNextAtom;
end else if CurPos.Flag=cafSemicolon then begin
break;
end else
RaiseExceptionFmt(20170421201056,ctsStrExpectedButAtomFound,[';',GetAtom]);
until false;
Result:=true;
end;
begin
Result:=false;
BuildTree(lsrImplementationUsesSectionEnd);
SourceChangeCache.MainScanner:=Scanner;
ExistingUnits:=nil;
try
// first collect all units
if not FindUsedUnitNames(ExistingUnits) then exit;
// then change uses sections
Replace(FindMainUsesNode);
Replace(FindImplementationUsesNode);
finally
ExistingUnits.Free;
end;
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.AddUnitToUsesSection(UsesNode: TCodeTreeNode;
const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
const
SpecialUnits: array[1..5] of string = (
'cmem',
'sharedmem',
'lineinfo',
'heaptrc',
'cthreads'
);
var
Beauty: TBeautifyCodeOptions;
function SpecialUnitPriority(Identifier: PChar): integer;
begin
Result:=Low(SpecialUnits);
while Result<=High(SpecialUnits) do begin
if CompareIdentifierPtrs(Pointer(Identifier),Pointer(SpecialUnits[Result]))=0 then
exit;
inc(Result);
end;
end;
function NextUseUnitNodeInSameBlock(Node: TCodeTreeNode): boolean;
var
p: LongInt;
begin
if Node.NextBrother=nil then exit(false);
if PositionsInSameLine(Src,Node.EndPos,Node.NextBrother.StartPos) then
begin
// uses on same line belongs to the same formatting block
exit(true);
end;
// check that there is no comment/directive between
p:=FindPrevNonSpace(Src,Node.NextBrother.StartPos-1);
if Src[p]<>',' then exit(false);
p:=FindPrevNonSpace(Src,p-1);
if p>Node.EndPos then exit(false);
if LineEndCount(Src,Node.EndPos,Node.NextBrother.StartPos,p)>1 then exit(false);
Result:=true;
end;
procedure AddUseUnit(Lines: TStrings; FirstIndent, Indent: integer;
const NewUses: string);
var
Line: string;
l: Integer;
begin
if Lines.Count=0 then begin
Lines.Add(NewUses);
exit;
end;
Line:=Lines[Lines.Count-1];
if (atIdentifier in Beauty.DoInsertSpaceAfter)
or (atComma in Beauty.DoInsertSpaceInFront) then
Line:=Line+' ';
Line:=Line+',';
l:=length(Line)+length(NewUses)+1; // +1 for the following , or ;
if (atComma in Beauty.DoInsertSpaceAfter)
or (atIdentifier in Beauty.DoInsertSpaceInFront) then
inc(l);
if Lines.Count=1 then
inc(l,FirstIndent);
//DebugLn(['AddUseUnit Lines.Count=',Lines.Count,' l=',l,' Line="',Line,'" NewUses=',NewUses,' FirstIndent=',FirstIndent]);
if l<=Beauty.LineLength then begin
// append to last line
if (atComma in Beauty.DoInsertSpaceAfter)
or (atIdentifier in Beauty.DoInsertSpaceInFront) then
Line:=Line+' ';
Line:=Line+NewUses;
Lines[Lines.Count-1]:=Line;
end else begin
// add new line
Lines[Lines.Count-1]:=Line;
Line:=Beauty.GetIndentStr(Indent)+NewUses;
Lines.Add(Line);
end;
end;
var
LineStart, LineEnd, Indent, InsertPos, InsertToPos, InsertLen: integer;
NewUsesTerm: string;
InsertBehind: Boolean;
InsertNode: TCodeTreeNode;
Node: TCodeTreeNode;
NewCode: TCodeBuffer;
DiffPath: String;
DiffCnt: Integer;
BestDiffCnt: LongInt;
AnUnitName: String;
AnUnitInFilename: String;
i: Integer;
NewFilename: String;
NewComma: string;
Lines: TStringList;
FirstIndent: Integer;
InsertCode: String;
UsesInsertPolicy: TUsesInsertPolicy;
Prio: LongInt;
FirstNormalUsesNode: TCodeTreeNode;
InsertPosFound, CheckSpecialUnits: Boolean;
begin
Result:=false;
if (UsesNode=nil) or (UsesNode.Desc<>ctnUsesSection)
or (UsesNode.StartPos<1) or (UsesNode.EndPos<1)
or (not IsDottedIdentifier(NewUnitName))
then exit;
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// find nice insert position
Prio:=SpecialUnitPriority(PChar(NewUnitName));
UsesInsertPolicy:=Beauty.UsesInsertPolicy;
if aufLast in Flags then
UsesInsertPolicy:=uipLast;
InsertPosFound:=false;
CheckSpecialUnits:=not (aufNotCheckSpecialUnit in Flags);
if CheckSpecialUnits and (Prio<=High(SpecialUnits)) then begin
// this is a special unit, insert at the beginning
InsertBehind:=false;
InsertNode:=UsesNode.FirstChild;
while (InsertNode<>nil)
and (Prio>SpecialUnitPriority(@Src[InsertNode.StartPos])) do
InsertNode:=InsertNode.NextBrother;
InsertPosFound:=true;
if InsertNode=nil then begin
InsertBehind:=true;
InsertNode:=UsesNode.LastChild;
end;
end;
if not InsertPosFound then begin
FirstNormalUsesNode:=UsesNode.FirstChild;
if CheckSpecialUnits and (UsesInsertPolicy<>uipLast) then begin
while (FirstNormalUsesNode<>nil)
and (SpecialUnitPriority(@Src[FirstNormalUsesNode.StartPos])<Prio) do
FirstNormalUsesNode:=FirstNormalUsesNode.NextBrother;
if FirstNormalUsesNode=nil then
UsesInsertPolicy:=uipLast;
end;
case UsesInsertPolicy of
uipFirst:
begin
InsertBehind:=false;
InsertNode:=FirstNormalUsesNode;
end;
uipInFrontOfRelated,uipBehindRelated:
begin
if UsesInsertPolicy=uipBehindRelated then begin
InsertNode:=UsesNode.LastChild;
InsertBehind:=true;
end else begin
InsertBehind:=false;
InsertNode:=FirstNormalUsesNode;
end;
NewCode:=FindUnitSource(NewUnitName,'',false);
if NewCode<>nil then begin
NewFilename:=NewCode.Filename;
BestDiffCnt:=High(integer);
Node:=FirstNormalUsesNode;
while Node<>nil do begin
AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
// search unit
//DebugLn(['TStandardCodeTool.AddUnitToUsesSection Unit=',AnUnitName,' in "',AnUnitInFilename,'"']);
NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false);
if NewCode<>nil then begin
// used unit found -> compute distance
DiffPath:=CreateRelativePath(NewCode.Filename,ExtractFilePath(NewFilename));
DiffCnt:=0;
for i:=1 to length(DiffPath) do
if DiffPath[i]=PathDelim then
inc(DiffCnt);
//DebugLn(['TStandardCodeTool.AddUnitToUsesSection DiffCnt=',DiffCnt,' "',NewCode.Filename,'" "',NewFilename,'"']);
if UsesInsertPolicy=uipInFrontOfRelated then begin
// insert in front of the first node with the lowest DiffCnt
if BestDiffCnt>DiffCnt then begin
BestDiffCnt:=DiffCnt;
InsertNode:=Node;
InsertBehind:=false;
end;
end else begin
// insert behind the last node with the lowest DiffCnt
if BestDiffCnt>=DiffCnt then begin
BestDiffCnt:=DiffCnt;
InsertNode:=Node;
InsertBehind:=true;
end;
end;
end;
Node:=Node.NextBrother;
end;
end;
end;
uipLast:
begin
InsertNode:=UsesNode.LastChild;
InsertBehind:=true;
end;
uipAlphabetically:
begin
InsertNode:=FirstNormalUsesNode;
InsertBehind:=false;
while (InsertNode<>nil)
and (CompareIdentifiers(PChar(NewUnitName),@Src[InsertNode.StartPos])<0) do
InsertNode:=InsertNode.NextBrother;
if InsertNode=nil then begin
InsertNode:=UsesNode.LastChild;
InsertBehind:=true;
end;
end;
end;
end;
// build insert text "newunitname in 'file'"
NewUsesTerm:=NewUnitName;
if NewUnitInFile<>'' then
NewUsesTerm:=NewUsesTerm+' '
+Beauty.BeautifyKeyWord('in')
+' '''+NewUnitInFile+'''';
NewComma:=',';
if (atComma in Beauty.DoInsertSpaceInFront)
or (atIdentifier in Beauty.DoInsertSpaceAfter)
then
NewComma:=' '+NewComma;
if (atComma in Beauty.DoInsertSpaceAfter)
or (atIdentifier in Beauty.DoInsertSpaceInFront)
then
NewComma:=NewComma+' ';
if InsertBehind then begin
// insert behind unit name, in front of semicolon or comma
// for example: uses unit1|, unit2 in 'unit2.pp'|;
InsertPos:=InsertNode.EndPos;
InsertCode:=NewComma+NewUsesTerm;
end else begin
// insert in front of unit name, behind 'uses' or comma
// for example: uses |unit1, |unit2;
InsertPos:=InsertNode.StartPos;
InsertCode:=NewUsesTerm+NewComma;
end;
InsertToPos:=InsertPos;
//DebugLn(['TStandardCodeTool.AddUnitToUsesSection InsertNode=',ExtractNode(InsertNode,[]),' InsertBehind=',InsertBehind]);
// check if addition fits into the line
// if not, rebuild the uses section
GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
InsertLen:=length(NewUsesTerm)+length(NewComma);
//DebugLn(['TStandardCodeTool.AddUnitToUsesSection Line=',copy(Src,LineStart,InsertPos-LineStart),'<InsertPos>',copy(Src,InsertPos,LineEnd-InsertPos),' NewLen=',LineEnd-LineStart+InsertLen,' Max=',Beauty.LineLength,' Addition=',NewUsesTerm]);
if (LineEnd-LineStart+InsertLen > Beauty.LineLength) then begin
// line too long => reformat block of used units
// find start of block of used units
Node:=InsertNode;
while (Node.PriorBrother<>nil)
and NextUseUnitNodeInSameBlock(Node.PriorBrother) do
Node:=Node.PriorBrother;
InsertPos:=Node.StartPos;
GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
FirstIndent:=InsertPos-LineStart;
Indent:=Beauty.GetLineIndent(Src,InsertPos);
if PositionsInSameLine(Src,UsesNode.StartPos,InsertPos) then begin
// for example: uses |unit1;
inc(Indent,Beauty.Indent);
end;
// create new block of used units
Lines:=TStringList.Create;
try
while Node<>nil do begin
InsertToPos:=Node.EndPos;
if (Node=InsertNode) and (not InsertBehind) then
AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
InsertCode:=ExtractUsedUnitName(Node);
if UpAtomIs('IN') then begin
ReadNextAtom;
InsertCode:=InsertCode+' '+Beauty.BeautifyKeyWord('in')+' '+GetAtom;
end;
AddUseUnit(Lines,FirstIndent,Indent,InsertCode);
if (Node=InsertNode) and InsertBehind then
AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
if not NextUseUnitNodeInSameBlock(Node) then break;
Node:=Node.NextBrother;
end;
InsertCode:='';
for i:=0 to Lines.Count-1 do begin
if i>0 then
InsertCode:=InsertCode+Beauty.LineEnd;
InsertCode:=InsertCode+Lines[i];
end;
finally
Lines.Free;
end;
end;
//DebugLn(['TStandardCodeTool.AddUnitToUsesSection Replace="',copy(Src,InsertPos,InsertToPos-InsertPos),'" with "',InsertCode,'"']);
if not SourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertToPos,
InsertCode) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.AddUnitToMainUsesSection(const NewUnitName,
NewUnitInFile: string; SourceChangeCache: TSourceChangeCache;
const Flags: TAddUsesFlags): boolean;
begin
Result:=AddUnitToSpecificUsesSection(usMain, NewUnitName, NewUnitInFile,
SourceChangeCache, Flags);
end;
function TStandardCodeTool.AddUnitToImplementationUsesSection(
const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
begin
Result:=AddUnitToSpecificUsesSection(usImplementation,
NewUnitName, NewUnitInFile, SourceChangeCache, Flags);
end;
function TStandardCodeTool.AddUnitToSpecificUsesSection(
UsesSection: TUsesSection; const NewUnitName, NewUnitInFile: string;
SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
var
UsesNode, OtherUsesNode, SectionNode, Node: TCodeTreeNode;
NewUsesTerm: string;
InsertPos: integer;
Junk: TAtomPosition;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if not IsDottedIdentifier(NewUnitName) then exit;
if UsesSection=usMain then begin
// quick check using only the main uses section
BuildTree(lsrMainUsesSectionEnd);
UsesNode:=FindMainUsesNode;
if (UsesNode<>nil)
and (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk)) then
exit(true); // unit already in main uses section
end;
if GetSourceType=ctnUnit then
BuildTree(lsrImplementationUsesSectionEnd)
else if UsesSection=usImplementation then begin
MoveCursorToNodeStart(Tree.Root);
RaiseException(20170421201102,
ctsCanNotAddAUnitToTheImplementationBecauseOnlyAUnitH);
end;
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
SourceChangeCache.BeginUpdate;
try
UsesNode:=FindMainUsesNode;
OtherUsesNode:=FindImplementationUsesNode;
if UsesSection=usImplementation then begin
SectionNode:=UsesNode;
UsesNode:=OtherUsesNode;
OtherUsesNode:=SectionNode;
end;
// remove unit from other uses section
if (OtherUsesNode<>nil) then
RemoveUnitFromUsesSection(OtherUsesNode,NewUnitName,SourceChangeCache);
if UsesNode<>nil then begin
// add unit to existing uses section
if not (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk))
then begin
if not AddUnitToUsesSection(UsesNode,NewUnitName,NewUnitInFile,
SourceChangeCache,Flags)
then
exit;
end;
end else begin
// create a new uses section
if Tree.Root=nil then exit;
SectionNode:=Tree.Root;
InsertPos:=0;
NewUsesTerm:='';
if SectionNode.Desc=ctnUnit then begin
// unit
case UsesSection of
usMain: SectionNode:=FindInterfaceNode;
usImplementation: SectionNode:=FindImplementationNode;
end;
if SectionNode<>nil then begin
// add uses to existing interface/implementation before any content
MoveCursorToNodeStart(SectionNode);
ReadNextAtom;
InsertPos := CurPos.EndPos;
end else begin
// section is missing => add it
SectionNode:=Tree.Root;
case UsesSection of
usMain: NewUsesTerm:='interface';
usImplementation: NewUsesTerm:='implementation';
end;
NewUsesTerm:=Beauty.BeautifyKeyWord(NewUsesTerm)
+Beauty.LineEnd;
if SectionNode.FirstChild<>nil then begin
// unit not empty => add in front of first node
InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.FirstChild.StartPos,
true);
end else begin
// unit empty => add at end
InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
end;
end;
end;
if InsertPos<1 then begin
// not a unit (i.e. program)
// => insert after title and directives
Node:=SectionNode.Next;
if (Node<>nil) and (Node.Desc=ctnSrcName) then
Node:=Node.NextSkipChilds;
if Node<>nil then begin
InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos,
true);
end else begin
// program empty => add at end
InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
end;
end;
NewUsesTerm:=NewUsesTerm+Beauty.BeautifyKeyWord('uses')+' '+NewUnitName;
if NewUnitInFile<>'' then
NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+''';'
else
NewUsesTerm:=NewUsesTerm+';';
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
NewUsesTerm) then exit;
end;
Result:=true;
finally
if not Result then
SourceChangeCache.Clear;
if not SourceChangeCache.EndUpdate then
Result:=false;
end;
end;
function TStandardCodeTool.UnitExistsInUsesSection(UsesSection: TUsesSection;
const AnUnitName: string): boolean;
var
UsesNode: TCodeTreeNode;
begin
Result:=false;
if not IsDottedIdentifier(AnUnitName) then
exit;
if UsesSection=usMain then
BuildTree(lsrMainUsesSectionEnd)
else
BuildTree(lsrImplementationUsesSectionEnd);
case UsesSection Of
usMain: UsesNode:=FindMainUsesNode;
usImplementation: UsesNode:=FindImplementationUsesNode;
end;
Result:=UnitExistsInUsesSection(UsesNode,AnUnitName);
end;
function TStandardCodeTool.UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
const AnUnitName: string): boolean;
begin
Result:=false;
if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
exit;
MoveCursorToNodeStart(UsesNode);
ReadNextAtom; // read 'uses'
repeat
ReadNextAtom; // read name
if not AtomIsIdentifier then exit;
if ReadAndCompareUsedUnit(AnUnitName) then begin
// unit found
exit(true);
end;
if UpAtomIs('IN') then begin
ReadNextAtom;
ReadNextAtom;
end;
if CurPos.Flag=cafSemicolon then break;
if CurPos.Flag<>cafComma then break;
until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
end;
function TStandardCodeTool.RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;
var UnitPos, StartPos, EndPos: integer;
Found: Boolean;
begin
Result:=false;
if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
exit;
MoveCursorToNodeStart(UsesNode);
ReadNextAtom; // read 'uses'
UnitPos:=0;
repeat
EndPos:=CurPos.StartPos;
ReadNextAtom; // read name
if not AtomIsIdentifier then exit;
inc(UnitPos);
StartPos:=CurPos.StartPos;
Found:=ReadAndCompareUsedUnit(AnUnitName);
if UpAtomIs('IN') then begin
ReadNextAtom;
ReadNextAtom;
end;
if Found then begin
// unit found
SourceChangeCache.MainScanner:=Scanner;
if UnitPos=1 then begin
// first unit in uses section
if AtomIsChar(';') then begin
// last unit in uses section -> delete whole uses section
StartPos:=FindLineEndOrCodeInFrontOfPosition(UsesNode.StartPos,true,true);
EndPos:=UsesNode.EndPos;
end else begin
// not last unit -> delete with comma behind
EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
end;
end else begin
// not first unit in uses section
if AtomIsChar(';') then begin
// last unit -> delete with comma in front
StartPos:=EndPos;
EndPos:=CurPos.StartPos;
end else if PositionsInSameLine(Src,EndPos,StartPos) then begin
// not first unit in line -> delete with comma in front
StartPos:=EndPos;
EndPos:=CurPos.StartPos;
end else begin
// first unit in line -> delete with comma behind
EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
end;
end;
if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
exit;
end;
if AtomIsChar(';') then break;
if not AtomIsChar(',') then break;
until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
Result:=true;
end;
function TStandardCodeTool.RemoveUnitFromAllUsesSections(
const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;
function RemoveFromSection(UsesNode: TCodeTreeNode): boolean;
begin
Result:=(UsesNode=nil)
or (RemoveUnitFromUsesSection(UsesNode,AnUnitName,SourceChangeCache));
end;
begin
Result:=false;
if (AnUnitName='') or (SourceChangeCache=nil) then exit;
BuildTree(lsrImplementationUsesSectionEnd);
SourceChangeCache.BeginUpdate;
try
if not RemoveFromSection(FindMainUsesNode) then exit;
if not RemoveFromSection(FindImplementationUsesNode) then exit;
finally
Result:=SourceChangeCache.EndUpdate;
end;
end;
function TStandardCodeTool.FixUsedUnitCase(
SourceChangeCache: TSourceChangeCache): boolean;
function FixUsesSection(UsesNode: TCodeTreeNode): boolean;
begin
Result:=(UsesNode=nil) or FixUsedUnitCaseInUsesSection(UsesNode,SourceChangeCache);
end;
begin
//debugln('TStandardCodeTool.FixUsedUnitCase ',MainFilename);
Result:=false;
BuildTree(lsrImplementationUsesSectionEnd);
if not FixUsesSection(FindMainUsesNode) then exit;
if not FixUsesSection(FindImplementationUsesNode) then exit;
Result:=true;
end;
function TStandardCodeTool.FixUsedUnitCaseInUsesSection(
UsesNode: TCodeTreeNode; SourceChangeCache: TSourceChangeCache): boolean;
function FindUnit(AFilename: string): string;
var
CurDir: String;
MakeRelative: Boolean;
begin
Result:='';
AFilename:=TrimFilename(AFilename);
CurDir:='';
if FilenameIsAbsolute(AFilename) then begin
MakeRelative:=false;
end else begin
MakeRelative:=true;
if TCodeBuffer(Scanner.MainCode).IsVirtual then exit;
CurDir:=ExtractFilePath(TCodeBuffer(Scanner.MainCode).Filename);
AFilename:=CurDir+AFilename;
end;
Result:=DirectoryCache.Pool.FindDiskFilename(AFilename,true);
if Result='' then exit;
if MakeRelative then
Result:=CreateRelativePath(Result,CurDir);
end;
var
UnitInFilename: String;
Changed: Boolean;
RealUnitInFilename: String;
UnitNameRange: TAtomPosition;
InAtom: TAtomPosition;
begin
Result:=false;
if (UsesNode=nil) then exit;
MoveCursorToNodeStart(UsesNode);
ReadNextAtom; // read 'uses'
Changed:=false;
repeat
ReadNextAtom; // read name
if not ReadNextUsedUnit(UnitNameRange,InAtom,false) then exit;
if InAtom.StartPos>1 then begin
UnitInFilename:=GetAtom(InAtom);
//debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection A UnitInFilename="',UnitInFilename,'"');
if (UnitInFilename<>'') and (UnitInFilename[1]='''') then begin
UnitInFilename:=copy(UnitInFilename,2,length(UnitInFilename)-2);
RealUnitInFilename:=FindUnit(UnitInFilename);
//debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection B RealUnitInFilename="',RealUnitInFilename,'"');
if (RealUnitInFilename<>'')
and (RealUnitInFilename<>UnitInFilename) then begin
if not Changed then begin
SourceChangeCache.MainScanner:=Scanner;
Changed:=true;
end;
debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection Replacing UnitInFilename="',UnitInFilename,'" with "',RealUnitInFilename,'"');
if not SourceChangeCache.Replace(gtNone,gtNone,
InAtom.StartPos,InAtom.EndPos,''''+RealUnitInFilename+'''') then exit;
end;
end;
end;
if CurPos.Flag=cafSemicolon then break;
if CurPos.Flag<>cafComma then exit;
until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
if Changed and (not SourceChangeCache.Apply) then exit;
Result:=true;
end;
function TStandardCodeTool.FindUsedUnitNames(var MainUsesSection,
ImplementationUsesSection: TStrings): boolean;
var
MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
begin
MainUsesSection:=nil;
ImplementationUsesSection:=nil;
// find the uses sections
BuildTree(lsrImplementationUsesSectionEnd);
MainUsesNode:=FindMainUsesNode;
ImplementatioUsesNode:=FindImplementationUsesNode;
// create lists
try
MainUsesSection:=UsesSectionToUnitNames(MainUsesNode);
ImplementationUsesSection:=UsesSectionToUnitNames(ImplementatioUsesNode);
except
FreeAndNil(MainUsesSection);
FreeAndNil(ImplementationUsesSection);
raise;
end;
Result:=true;
end;
function TStandardCodeTool.FindUsedUnitNames(var List: TStringToStringTree
): boolean;
procedure Collect(UsesNode: TCodeTreeNode; const Tag: string);
var
UnitNameAtom: TAtomPosition;
InAtom: TAtomPosition;
OldTag: string;
AnUnitName: String;
begin
if UsesNode=nil then exit;
MoveCursorToUsesStart(UsesNode);
repeat
// read next unit name
ReadNextUsedUnit(UnitNameAtom, InAtom);
AnUnitName:=GetAtom(UnitNameAtom);
// tag unit in list
OldTag:=List[AnUnitName];
if System.Pos(Tag,OldTag)<1 then
List[AnUnitName]:=OldTag+Tag;
if CurPos.Flag=cafComma then begin
// read next unit name
ReadNextAtom;
end else if CurPos.Flag=cafSemicolon then begin
break;
end else
RaiseExceptionFmt(20170421201120,ctsStrExpectedButAtomFound,[';',GetAtom]);
until false;
Result:=true;
end;
begin
// find the uses sections
List:=TStringToStringTree.Create(false);
BuildTree(lsrImplementationUsesSectionEnd);
Collect(FindMainUsesNode,'Main');
Collect(FindImplementationUsesNode,'Implementation');
Result:=true;
end;
function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection: TStrings
): boolean;
var
MainUsesNode: TCodeTreeNode;
begin
MainUsesSection:=nil;
// find the uses sections
BuildTree(lsrMainUsesSectionEnd);
MainUsesNode:=FindMainUsesNode;
// create lists
try
MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
except
FreeAndNil(MainUsesSection);
raise;
end;
Result:=true;
end;
function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection,
ImplementationUsesSection: TStrings): boolean;
var
MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
begin
MainUsesSection:=nil;
ImplementationUsesSection:=nil;
// find the uses sections
BuildTree(lsrImplementationUsesSectionEnd);
MainUsesNode:=FindMainUsesNode;
ImplementatioUsesNode:=FindImplementationUsesNode;
// create lists
try
MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
ImplementationUsesSection:=UsesSectionToFilenames(ImplementatioUsesNode);
except
FreeAndNil(MainUsesSection);
FreeAndNil(ImplementationUsesSection);
raise;
end;
Result:=true;
end;
{------------------------------------------------------------------------------
function TStandardCodeTool.FindDelphiProjectUnits(var FoundInUnits,
MissingInUnits, NormalUnits: TStrings): boolean;
Reads the main uses section and tries to find each unit file having
an 'in' modifier.
The associated objects in the list will be the found codebuffers.
FoundInUnits returns the list of found 'in' unitnames plus TCodeBuffer
MissingInUnits returns the list of missing 'in' unitnames
NormalUnits returns the list of unitnames plus TCodeBuffer (if found)
If no codebuffer was found/created then the filename will be the unit name
plus the 'in' extension.
------------------------------------------------------------------------------}
function TStandardCodeTool.FindDelphiProjectUnits(out FoundInUnits,
MissingInUnits, NormalUnits: TStrings; UseContainsSection: boolean;
IgnoreNormalUnits: boolean): boolean;
var
AnUnitName, AnUnitInFilename: string;
NewCode: TCodeBuffer;
UsesNode: TCodeTreeNode;
Node: TCodeTreeNode;
begin
Result:=false;
FoundInUnits:=nil;
MissingInUnits:=nil;
NormalUnits:=nil;
// find the uses sections
BuildTree(lsrMainUsesSectionEnd);
UsesNode:=FindMainUsesNode(UseContainsSection);
if UsesNode=nil then exit;
FoundInUnits:=TStringListUTF8Fast.Create;
MissingInUnits:=TStringListUTF8Fast.Create;
if IgnoreNormalUnits then
NormalUnits:=nil
else
NormalUnits:=TStringListUTF8Fast.Create;
Node:=UsesNode.FirstChild;
while Node<>nil do begin
// read next unit name
AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
// find unit file
if AnUnitInFilename<>'' then begin
// An 'in' unit => Delphi project file
NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
if (NewCode=nil) then begin
// no source found
MissingInUnits.Add(AnUnitName+' in '+AnUnitInFilename);
end else begin
// source found => add filename to list
FoundInUnits.AddObject(AnUnitName+' in '+AnUnitInFilename,NewCode);
end;
end else if AnUnitName<>'' then begin
// the units without 'in' are 'Forms' or units added by the user
if not IgnoreNormalUnits then begin
NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
NormalUnits.AddObject(AnUnitName,NewCode);
end;
end;
Node:=Node.NextBrother;
end;
Result:=true;
end;
{------------------------------------------------------------------------------
function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
): TStrings;
Reads the uses section backwards and tries to find each unit file.
The associated objects in the list will be the found codebuffers.
If no codebuffer was found/created then the filename will be the unit name
plus the 'in' extension.
------------------------------------------------------------------------------}
function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
): TStrings;
var
AnUnitName, AnUnitInFilename: string;
NewCode: TCodeBuffer;
UnitFilename: string;
Node: TCodeTreeNode;
begin
Result:=TStringList.Create;
if UsesNode=nil then exit;
Node:=UsesNode.LastChild;
while Node<>nil do begin
// read unit name
AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
if AnUnitName<>'' then begin
// find unit file
NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
if (NewCode=nil) then begin
// no source found
UnitFilename:=AnUnitName;
if AnUnitInFilename<>'' then
UnitFilename:=UnitFilename+' in '+AnUnitInFilename;
end else begin
// source found
UnitFilename:=NewCode.Filename;
end;
// add filename to list
Result.AddObject(UnitFilename,NewCode);
end;
Node:=Node.PriorBrother;
end;
end;
function TStandardCodeTool.UsesSectionToUnitnames(UsesNode: TCodeTreeNode
): TStrings;
var
AnUnitName: string;
Node: TCodeTreeNode;
begin
Result:=TStringList.Create;
if UsesNode=nil then exit;
Node:=UsesNode.LastChild;
while Node<>nil do begin
// read unit name
AnUnitName:=ExtractUsedUnitName(Node);
if AnUnitName<>'' then
Result.Add(AnUnitName);
Node:=Node.PriorBrother;
end;
end;
function TStandardCodeTool.FindMissingUnits(var MissingUnits: TStrings;
FixCase: boolean; SearchImplementation: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
const
FPCSrcSearchRequiresPPU = true;
function CheckUsesSection(UsesNode: TCodeTreeNode): boolean;
var
OldUnitName: String;
OldInFilename: String;
AFilename: String;
s: String;
NewUnitName: String;
NewInFilename: String;
FromPos: LongInt;
ToPos: LongInt;
Node: TCodeTreeNode;
begin
if UsesNode=nil then exit(true);
Node:=UsesNode.FirstChild;
while Node<>nil do begin
// read next unit name
OldUnitName:=ExtractUsedUnitName(Node,@OldInFilename);
// find unit file
NewUnitName:=OldUnitName;
NewInFilename:=OldInFilename;
//debugln(['CheckUsesSection NewUnitName="',NewUnitName,'" NewInFilename="',NewInFilename,'"']);
AFilename:=DirectoryCache.FindUnitSourceInCompletePath(
NewUnitName,NewInFilename,true,FPCSrcSearchRequiresPPU,AddedNameSpace);
s:=NewUnitName;
if NewInFilename<>'' then
s:=s+' in '''+NewInFilename+'''';
if AFilename<>'' then begin
// unit found
if FixCase
and ((NewUnitName<>OldUnitName) or (NewInFilename<>OldInFilename)) then
begin
// fix case
FromPos:=Node.StartPos;
ToPos:=Node.EndPos;
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,s);
DebugLn('TStandardCodeTool.FindMissingUnits.CheckUsesSection fix case Unit Name(',OldUnitName,'->',NewUnitName,') InFile(',OldInFilename,'->',NewInFilename,')');
end;
end else begin
// unit not found
if MissingUnits=nil then MissingUnits:=TStringList.Create;
MissingUnits.Add(s);
end;
Node:=Node.NextBrother;
end;
Result:=true;
end;
begin
Result:=false;
BuildTree(lsrImplementationUsesSectionEnd);
if FixCase then
SourceChangeCache.MainScanner:=Scanner;
try
if not CheckUsesSection(FindMainUsesNode) then exit;
if SearchImplementation
and not CheckUsesSection(FindImplementationUsesNode) then exit;
except
FreeAndNil(MissingUnits);
raise;
end;
if FixCase then
Result:=SourceChangeCache.Apply
else
Result:=true;
end;
function TStandardCodeTool.CommentUnitsInUsesSection(MissingUnits: TStrings;
SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
// Examples:
// 1. uses {a,} b, c; commenting one unit not at end
// 2. uses a, {b,} c; commenting one unit not at end
// 3. uses {a, b,} c; commenting several units not at end
// 4. uses a{, b, c} ; commenting units at end
// 5. {uses a, b, c;} commenting all units
// 6. uses {a,} b{, c}; commenting several units
procedure Comment(StartPos, EndPos: integer);
begin
//debugln(['Comment ',dbgstr(copy(Src,StartPos,EndPos-StartPos))]);
CommentCode(StartPos,EndPos,SourceChangeCache,false);
end;
var
i: Integer;
CurUnitName: String;
CommentCurUnit: Boolean;
FirstCommentUnitStart: Integer;
LastCommaAfterCommentUnitsStart: Integer;
LastNormalUnitEnd: Integer;
LastCommentUnitEnd: Integer;
Node: TCodeTreeNode;
begin
Result:=true;
if UsesNode=nil then exit;
FirstCommentUnitStart:=-1;
LastCommaAfterCommentUnitsStart:=-1;
LastNormalUnitEnd:=-1;
LastCommentUnitEnd:=-1;
Node:=UsesNode.FirstChild;
while Node<>nil do begin
// check if unit should be commented
CurUnitName:=ExtractUsedUnitName(Node);
// Note: CurPos is now on atom behind used unit, i.e. comma or semicolon
i:=MissingUnits.Count-1;
while (i>=0)
and (CompareIdentifiers(PChar(Pointer(MissingUnits[i])),
PChar(Pointer(CurUnitName)))<>0) do
dec(i);
CommentCurUnit:=i>=0;
//debugln('CommentUnitsInUsesSection CurUnitName="',CurUnitName,'" CommentCurUnit=',dbgs(CommentCurUnit));
if CommentCurUnit then begin
// unit should be commented
if FirstCommentUnitStart<1 then FirstCommentUnitStart:=Node.StartPos;
LastCommentUnitEnd:=Node.EndPos;
end else begin
// unit should be kept
LastNormalUnitEnd:=Node.EndPos;
if FirstCommentUnitStart>=1 then begin
// there are some units to be commented
// See examples: 1., 2., 3. and 6.
Comment(FirstCommentUnitStart,LastCommaAfterCommentUnitsStart);
FirstCommentUnitStart:=-1;
LastCommentUnitEnd:=-1;
LastCommaAfterCommentUnitsStart:=-1;
end;
end;
if CommentCurUnit then
LastCommaAfterCommentUnitsStart:=CurPos.EndPos;
if CurPos.Flag<>cafComma then begin
if CommentCurUnit then begin
// last unit must be commented
if LastNormalUnitEnd>=1 then begin
// comment last unit and keep some units in front
// See example: 4.
Comment(LastNormalUnitEnd,LastCommentUnitEnd);
end else begin
// all units should be commented
// See example: 5.
Comment(UsesNode.StartPos,CurPos.EndPos);
end;
end;
break;
end;
Node:=Node.NextBrother;
end;
end;
function TStandardCodeTool.CommentUnitsInUsesSections(MissingUnits: TStrings;
SourceChangeCache: TSourceChangeCache): boolean;
begin
if (MissingUnits=nil) or (MissingUnits.Count=0) then
exit(true);
Result:=false;
BuildTree(lsrInitializationStart);
SourceChangeCache.MainScanner:=Scanner;
if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindMainUsesNode) then exit;
if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindImplementationUsesNode) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.FindUnusedUnits(Units: TStrings): boolean;
// returns a list of unitname=flags
// flags are a comma separated list of words:
// 'implementation': unit is in implementation uses section
// 'used': an identifier of the interface is used
// 'code': unit has non empty initialization/finalization section
var
Identifiers: TAVLTree;// all identifiers used in this unit
function IsUnitAlreadyChecked(const AnUnitName: string): boolean;
var
i: Integer;
begin
for i:=0 to Units.Count-1 do
if SysUtils.CompareText(Units.Names[i],AnUnitName)=0 then exit(true);
Result:=false;
end;
procedure GatherIdentifiersInRange(StartPos, EndPos: integer);
// gather all used identifiers from this unit in the range Startpos..EndPos
var
Identifier: PChar;
begin
if (StartPos<1) or (StartPos>=EndPos) then exit;
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
if CurPos.StartPos>=EndPos then break;
if IsIdentStartChar[Src[CurPos.StartPos]] then begin
Identifier:=@Src[CurPos.StartPos];
if Identifiers.Find(Identifier)=nil then begin
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['Used Identifier=',GetIdentifier(Identifier)]);
{$ENDIF}
Identifiers.Add(Identifier);
end;
end;
until false;
end;
procedure GatherIdentifiers;
// gather all used identifiers from this unit
var
StartPos: Integer;
procedure Gather(EndPos: integer);
begin
if StartPos<1 then exit;
GatherIdentifiersInRange(StartPos,EndPos);
StartPos:=-1;
end;
var
Node: TCodeTreeNode;
begin
if Identifiers<>nil then exit;
Identifiers:=TAVLTree.Create(@CompareIdentifierPtrs);
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['GatherIdentifiers ']);
{$ENDIF}
StartPos:=-1;
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnUseUnit,ctnUsesSection,ctnUseUnitNamespace,ctnUseUnitClearName,
ctnProgram,ctnUnit,ctnPackage,ctnLibrary,ctnEndPoint:
begin
// skip node
Gather(Node.StartPos);
end;
ctnEnumIdentifier,
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition:
begin
// start reading behind identifier
Gather(Node.StartPos);
MoveCursorToCleanPos(Node.StartPos);
ReadNextAtom;
StartPos:=CurPos.EndPos;
end;
else
if StartPos<1 then
StartPos:=Node.StartPos;
end;
Node:=Node.Next;
end;
end;
function InterfaceIsUsed(Tool: TFindDeclarationTool;
IntfNode: TCodeTreeNode): boolean;
function IsIdentifierUsed(StartPos: integer): boolean;
begin
{$IFDEF VerboseFindUnusedUnits}
if CompareIdentifiers(PChar(GetIdentifier(@Tool.Src[StartPos])),'TComponent')=0 then
DebugLn(['IsIdentifierUsed ',GetIdentifier(@Tool.Src[StartPos])]);
{$ENDIF}
Result:=Identifiers.Find(@Tool.Src[StartPos])<>nil;
end;
function IsNodeVisible(Node: TCodeTreeNode): boolean;
begin
Result:=false;
Node:=Node.Parent;
while Node<>nil do begin
if Node.Desc=ctnInterface then exit(true);
if Node.Desc in AllClasses then exit;
Node:=Node.Parent;
end;
end;
function FindProceduresInHelper(Node: TCodeTreeNode): boolean;
begin
Result := False;
Node := Node.FirstChild;
while (Node<>nil) and (Node.Desc <> ctnEndPoint) do
begin
if (Node.Desc = ctnProcedure) and (Node.FirstChild<>nil) then
begin
if IsIdentifierUsed(Node.FirstChild.StartPos) then
Exit(True);
end;
Node:=Node.Next;
end;
end;
var
Node: TCodeTreeNode;
begin
Result:=true;
Node:=IntfNode.FirstChild;
while Node<>nil do begin
case Node.Desc of
ctnTypeHelper, ctnRecordHelper, ctnClassHelper:
if FindProceduresInHelper(Node) then exit;
ctnEnumIdentifier:
if IsIdentifierUsed(Node.StartPos) then exit;
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnGenericName:
if IsNodeVisible(Node) and IsIdentifierUsed(Node.StartPos) then exit;
ctnProcedure:
if (Node.Parent.Desc=ctnInterface)
and (Node.FirstChild<>nil)
and (Node.FirstChild.Desc=ctnProcedureHead)
and IsIdentifierUsed(Node.FirstChild.StartPos) then exit;
ctnGlobalProperty:
if Tool.MoveCursorToPropName(Node)
and IsIdentifierUsed(Tool.CurPos.StartPos) then exit;
end;
Node:=Node.Next;
end;
Result:=false;
end;
procedure CheckUnit(Tool: TFindDeclarationTool;
out HasCode, UseInterface: boolean);
var
Node: TCodeTreeNode;
Identifier: String;
begin
GatherIdentifiers;
HasCode:=false;
UseInterface:=false;
// parse used unit
Tool.BuildTree(lsrEnd);
Node:=Tool.Tree.Root;
while (Node<>nil) do begin
case Node.Desc of
ctnUnit,ctnPackage,ctnLibrary:
begin
Identifier:=Tool.ExtractSourceName;
if Identifiers.Find(PChar(Identifier))<>nil then
UseInterface:=true;
end;
ctnInterface:
if not UseInterface then
UseInterface:=InterfaceIsUsed(Tool,Node);
ctnInitialization,ctnFinalization,ctnBeginBlock:
begin
HasCode:=true;
break;
end;
end;
Node:=Node.NextBrother;
end;
end;
procedure CheckUsesSection(UsesNode: TCodeTreeNode; InImplementation: boolean);
var
Unit_Name: String;
UnitInFilename: String;
Tool: TFindDeclarationTool;
HasCode: boolean;
UseInterface: boolean;
Flags: String;
Node: TCodeTreeNode;
begin
HasCode:=false;
UseInterface:=false;
if UsesNode=nil then exit;
Node:=UsesNode.FirstChild;
while Node<>nil do begin
Unit_Name:=ExtractUsedUnitName(Node,@UnitInFilename);
if not IsUnitAlreadyChecked(Unit_Name) then begin
// try to load the used unit
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['CheckUsesSection ',Unit_Name,' in ',UnitInFilename]);
{$ENDIF}
Tool:=FindCodeToolForUsedUnit(Unit_Name,UnitInFilename,true);
// parse the used unit
CheckUnit(Tool,HasCode,UseInterface);
Flags:='';
if InImplementation then
Flags:=Flags+',implementation';
if HasCode then
Flags:=Flags+',code';
if UseInterface then
Flags:=Flags+',used';
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['CheckUsesSection ',Unit_Name,'=',Flags]);
{$ENDIF}
Units.Add(Unit_Name+'='+Flags);
end;
Node:=Node.NextBrother;
end;
end;
begin
Result:=false;
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['TStandardCodeTool.FindUnusedUnits START']);
{$ENDIF}
BuildTree(lsrEnd);
Identifiers:=nil;
try
CheckUsesSection(FindMainUsesNode,false);
CheckUsesSection(FindImplementationUsesNode,true);
finally
Identifiers.Free;
end;
{$IFDEF VerboseFindUnusedUnits}
DebugLn(['TStandardCodeTool.FindUnusedUnits END']);
{$ENDIF}
Result:=true;
end;
function TStandardCodeTool.FindNextIncludeInInitialization(
var LinkIndex: integer): TCodeBuffer;
// LinkIndex < 0 -> search first
var
InitializationNode: TCodeTreeNode;
StartCode: TCodeBuffer;
begin
Result:=nil;
if LinkIndex<0 then begin
BuildTree(lsrEnd);
InitializationNode:=FindInitializationNode;
if InitializationNode=nil then exit;
LinkIndex:=Scanner.LinkIndexAtCleanPos(InitializationNode.StartPos);
end else begin
InitializationNode:=nil;
inc(LinkIndex);
end;
if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) then exit;
StartCode:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
// ToDo: Fix the test for InitializationNode, it can be Nil.
while (LinkIndex<Scanner.LinkCount)
and (Scanner.Links[LinkIndex].CleanedPos<InitializationNode.EndPos) do begin
Result:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
if (Result<>StartCode) then
exit;
inc(LinkIndex);
end;
Result:=nil;
end;
function TStandardCodeTool.FindLazarusResourceInBuffer(
ResourceCode: TCodeBuffer; const ResourceName: string): TAtomPosition;
var ResNameCode: string;
function ReadLazResource: boolean;
begin
Result:=false;
if not ReadNextAtomIsChar('.') then exit;
if not ReadNextUpAtomIs('ADD') then exit;
if not ReadNextAtomIsChar('(') then exit;
ReadNextAtom;
if not AtomIsStringConstant then exit;
if UpAtomIs(ResNameCode) then
Result:=true;
repeat
ReadNextAtom;
until (CurPos.StartPos>SrcLen) or (AtomIsChar(')'));
ReadNextAtom; // read ';'
end;
var CleanPos, MaxCleanPos: integer;
begin
Result.StartPos:=-1;
if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255) then
exit;
if Scanner.CursorToCleanPos(1,ResourceCode,CleanPos)<>0 then exit;
if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
MaxCleanPos)<>0 then
MaxCleanPos:=-1;
MoveCursorToCleanPos(CleanPos);
ResNameCode:=''''+UpperCaseStr(ResourceName)+'''';
// search "LazarusResources.Add('<ResourceName>',"
repeat
ReadNextAtom; // read 'LazarusResources'
if UpAtomIs('LAZARUSRESOURCES') then begin
Result.StartPos:=CurPos.StartPos;
if ReadLazResource then begin
Result.EndPos:=CurPos.EndPos;
exit;
end;
end;
until (CurPos.StartPos>SrcLen) or UpAtomIs('END')
or ((MaxCleanPos>0) and (CurPos.StartPos>MaxCleanPos));
Result.StartPos:=-1;
end;
function TStandardCodeTool.FindLazarusResource(
const ResourceName: string): TAtomPosition;
// search Resource in all include files
var LinkIndex: integer;
CurCode: TCodeBuffer;
begin
Result.StartPos:=-1;
Result.EndPos:=-1;
Result.Flag:=cafNone;
LinkIndex:=-1;
CurCode:=FindNextIncludeInInitialization(LinkIndex);
while (CurCode<>nil) do begin
Result:=FindLazarusResourceInBuffer(CurCode,ResourceName);
if Result.StartPos>0 then exit;
CurCode:=FindNextIncludeInInitialization(LinkIndex);
end;
end;
function TStandardCodeTool.AddLazarusResource(ResourceCode: TCodeBuffer;
const ResourceName, ResourceData: string;
SourceChangeCache: TSourceChangeCache): boolean;
// ResoureData is the complete LazarusResource Statement
var FromPos, ToPos, i: integer;
OldPosition: TAtomPosition;
begin
Result:=false;
if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
or (ResourceData='') or (SourceChangeCache=nil) then exit;
BuildTree(lsrEnd);
SourceChangeCache.MainScanner:=Scanner;
OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
if OldPosition.StartPos>0 then begin
// replace old resource
FromPos:=OldPosition.StartPos;
ToPos:=OldPosition.EndPos;
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
ResourceData) then exit;
end else begin
// insert new resource
if ResourceCode.SourceLength>0 then begin
if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
FromPos)<>0 then exit;
inc(FromPos);
end else begin
// resource code empty -> can not be found in cleaned code
// special replace
i:=0;
while (i<Scanner.LinkCount)
and (Scanner.Links[i].Code<>Pointer(ResourceCode)) do
inc(i);
if i>=Scanner.LinkCount then exit;
FromPos:=Scanner.Links[i].CleanedPos;
end;
if not SourceChangeCache.ReplaceEx(gtNewLine,gtNewLine,FromPos,FromPos,
ResourceCode,ResourceCode.SourceLength+1,ResourceCode.SourceLength+1,
ResourceData)
then exit;
end;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.RemoveLazarusResource(ResourceCode: TCodeBuffer;
const ResourceName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var OldPosition: TAtomPosition;
begin
Result:=false;
if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
or (SourceChangeCache=nil) then exit;
BuildTree(lsrEnd);
SourceChangeCache.MainScanner:=Scanner;
OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
if OldPosition.StartPos>0 then begin
OldPosition.StartPos:=FindLineEndOrCodeInFrontOfPosition(
OldPosition.StartPos);
OldPosition.EndPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
if not SourceChangeCache.Replace(gtNone,gtNone,
OldPosition.StartPos,OldPosition.EndPos,'') then exit;
end;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.RenameInclude(LinkIndex: integer;
const NewFilename: string; KeepPath: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
{ change filename in an include directive
if KeepPath is true and the include dircetive contains a path
(relative or absolute), then this path is kept and only the filename is
replaced
}
var IncludeStart, IncludeEnd, FileStart, FileNameStart, FileEnd: integer;
begin
Result:=false;
if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) or (NewFileName='')
or (KeepPath and (length(NewFilename)>255))
or (SourceChangeCache=nil) then exit;
// find include directive
IncludeEnd:=Scanner.Links[LinkIndex].CleanedPos;
IncludeStart:=IncludeEnd-1;
if IncludeStart<1 then exit;
FileEnd:=0;
case Src[IncludeStart] of
'}':
begin
FileEnd:=IncludeStart;
dec(IncludeStart);
while (IncludeStart>0) and (Src[IncludeStart]<>'{') do
dec(IncludeStart);
end;
')':
begin
dec(IncludeStart);
FileEnd:=IncludeStart;
while (IncludeStart>1)
and ((Src[IncludeStart]<>'*') or (Src[IncludeStart-1]<>'(')) do
dec(IncludeStart);
end;
#13,#10:
begin
FileEnd:=IncludeStart;
if (FileEnd>0) and (IsLineEndChar[Src[FileEnd]]) then dec(FileEnd);
dec(IncludeStart);
while (IncludeStart>1)
and ((Src[IncludeStart]<>'/') or (Src[IncludeStart-1]<>'/')) do
dec(IncludeStart);
end;
end;
if IncludeStart<1 then exit;
FileStart:=IncludeStart;
while (FileStart<IncludeEnd) and (Src[FileStart]<>'$') do
inc(FileStart);
while (FileStart<IncludeEnd) and (not (IsSpaceChar[Src[FileStart]])) do
inc(FileStart);
while (FileStart<IncludeEnd) and (IsSpaceChar[Src[FileStart]]) do
inc(FileStart);
if FileStart>=IncludeEnd then exit;
SourceChangeCache.MainScanner:=Scanner;
if KeepPath then begin
FileNameStart:=FileEnd;
while (FileNameStart>FileStart) and (Src[FileNameStart]<>PathDelim) do
dec(FileNameStart);
if Src[FileNameStart]=PathDelim then
FileStart:=FileNameStart+1;
end;
if not SourceChangeCache.Replace(gtNone,GtNone,FileStart,FileEnd,
NewFilename) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
const OnFindDefineProperty: TOnFindDefinePropertyForContext;
RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
ObjectsMustExist: boolean): boolean;
var
RootContext: TFindContext;
function CreateFootNote(const Context: TFindContext): string;
var
Caret: TCodeXYPosition;
begin
Result:='. See '+Context.Tool.MainFilename;
if Context.Tool.CleanPosToCaret(Context.Node.StartPos,Caret) then
Result:=Result+'('+IntToStr(Caret.Y)+','+IntToStr(Caret.X)+')';
end;
function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
const ClassContext: TFindContext; ContextIsDefault: boolean): boolean; forward;
function FindNonPublishedDefineProperty(LFMNode: TLFMTreeNode;
DefaultErrorPosition: integer;
const IdentName: string; const ClassContext: TFindContext): boolean;
// properties can be defined via DefineProperties
var
PropertyNode: TLFMPropertyNode;
ObjectNode: TLFMObjectNode;
AncestorClassContext: TFindContext;
Params: TFindDeclarationParams;
IsDefined: Boolean;
begin
Result:=false;
if (not (LFMNode is TLFMPropertyNode)) then exit;
PropertyNode:=TLFMPropertyNode(LFMNode);
if (PropertyNode.Parent=nil)
or (not (PropertyNode.Parent is TLFMObjectNode)) then exit;
ObjectNode:=TLFMObjectNode(PropertyNode.Parent);
// find define property
IsDefined:=false;
if Assigned(OnFindDefineProperty) then begin
AncestorClassContext:=CleanFindContext;
if ClassContext.Tool=Self then begin
// the class is defined in this source
// -> try to find the ancestor class
if ObjectNode.AncestorContextValid then begin
AncestorClassContext:=CreateFindContext(
TFindDeclarationTool(ObjectNode.AncestorTool),
TCodeTreeNode(ObjectNode.AncestorNode));
end else begin
{$IFDEF VerboseCheckLFM}
debugln('FindNonPublishedDefineProperty Class is defined in this source: search ancestor ... ');
{$ENDIF}
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
fdfExceptionOnPredefinedIdent];
Params.ContextNode:=ClassContext.Node;
try
if ClassContext.Tool.FindAncestorOfClass(ClassContext.Node,
Params,true) then
begin
{$IFDEF VerboseCheckLFM}
debugln('FindNonPublishedDefineProperty Ancestor found');
{$ENDIF}
AncestorClassContext:=CreateFindContext(Params);
ObjectNode.AncestorTool:=AncestorClassContext.Tool;
ObjectNode.AncestorNode:=AncestorClassContext.Node;
end;
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
finally
Params.Free;
end;
ObjectNode.AncestorContextValid:=true;
end;
end;
OnFindDefineProperty(Self,ClassContext,AncestorClassContext,LFMNode,
IdentName,IsDefined);
if IsDefined then begin
//debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' IdentName="',IdentName,'"');
end else begin
{$IFDEF VerboseCheckLFM}
debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' NO DEFINE PROPERTIES');
{$ENDIF}
if DefaultErrorPosition=0 then ;
end;
end;
Result:=IsDefined;
end;
function FindLFMIdentifier(LFMNode: TLFMTreeNode;
DefaultErrorPosition: integer;
const IdentName: string; const ClassContext: TFindContext;
SearchInDefinePropertiesToo, ErrorOnNotFound: boolean;
out IdentContext: TFindContext): boolean;
var
Params: TFindDeclarationParams;
IsPublished, IsMissingInCode: Boolean;
CurContext: TFindContext;
begin
Result:=false;
IdentContext:=CleanFindContext;
IsPublished:=false;
if (ClassContext.Node=nil)
or (not (ClassContext.Node.Desc in AllClasses)) then begin
DebugLn('TStandardCodeTool.CheckLFM.FindLFMIdentifier Internal error');
exit;
end;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
fdfIgnoreOverloadedProcs];
Params.ContextNode:=ClassContext.Node;
Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
try
{DebugLn('FindLFMIdentifier A ',
' Ident=',
'"'+GetIdentifier(Params.Identifier)+'"',
' Context="'+ClassContext.Node.DescAsString,'" "',StringToPascalConst(copy(ClassContext.Tool.Src,ClassContext.Node.StartPos,20))+'"',
' File="'+ExtractFilename(ClassContext.Tool.MainFilename)+'"',
' Flags=['+FindDeclarationFlagsAsString(Params.Flags)+']'
);}
if ClassContext.Tool.FindIdentifierInContext(Params) then begin
IdentContext:=CleanFindContext;
repeat
CurContext:=CreateFindContext(Params);
if (not IsPublished)
and (CurContext.Node.HasParentOfType(ctnClassPublished)) then
IsPublished:=true;
if (IdentContext.Node=nil) then begin
if (LFMNode.TheType<>lfmnProperty)
or ((CurContext.Node.Desc=ctnProperty)
and (not CurContext.Tool.PropNodeIsTypeLess(CurContext.Node)))
then
IdentContext:=CurContext;
end;
if (IdentContext.Node<>nil) and IsPublished then break;
// search further
Params.Clear;
Params.Flags:=[fdfSearchInAncestors,
fdfIgnoreMissingParams,
fdfIgnoreCurContextNode,
fdfIgnoreOverloadedProcs];
Params.ContextNode:=CurContext.Node.Parent;
while (Params.ContextNode<>nil)
and (not (Params.ContextNode.Desc in AllClasses)) do
Params.ContextNode:=Params.ContextNode.Parent;
if Params.ContextNode=nil then break;
Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
if not CurContext.Tool.FindIdentifierInContext(Params) then
begin
DebugLn(['FindLFMIdentifier ERROR ancestor of '+LFMNode.GetPath+' not found: ',FindContextToString(IdentContext),' IdentName=',IdentName]);
break;
end;
until Params.NewNode=nil;
end;
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
finally
Params.Free;
end;
IsMissingInCode := False;
if (IdentContext.Node<>nil) and IsPublished then begin
Result:=true;
end else begin
// no proper node found -> search in DefineProperties
if SearchInDefinePropertiesToo then begin
if FindNonPublishedDefineProperty(LFMNode,DefaultErrorPosition,IdentName,ClassContext)
then begin
//debugln(['FindLFMIdentifier "',IdentName,'" is defined via DefineProperties']);
Result:=true;
end;
end
else
IsMissingInCode := True;
end;
if (not Result) and ErrorOnNotFound then begin
if (IdentContext.Node<>nil) and (not IsPublished) then begin
LFMTree.AddError(lfmeIdentifierNotPublished,LFMNode,
'identifier '+IdentName+' is not published in class '
+'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
DefaultErrorPosition);
end
else if IsMissingInCode then begin
LFMTree.AddError(lfmeIdentifierMissingInCode,LFMNode,
'identifier '+IdentName+' not found in pascal code '
+'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
DefaultErrorPosition);
end
else begin
LFMTree.AddError(lfmeIdentifierNotFound,LFMNode,
'identifier '+IdentName+' not found in class '
+'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
DefaultErrorPosition);
end;
end;
end;
function FindClassNodeForLFMObject(LFMObject: TLFMObjectNode;
const VarPropContext: TFindContext): TFindContext;
var
Params: TFindDeclarationParams;
TypeNode: TCodeTreeNode;
VariableTypeName, AnUnitName, TypeName: String;
begin
Result:=CleanFindContext;
// check if identifier is a variable or property
VariableTypeName:='';
if (VarPropContext.Node.Desc=ctnVarDefinition) then begin
TypeNode:=VarPropContext.Tool.FindTypeNodeOfDefinition(VarPropContext.Node);
if TypeNode=nil then begin
LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
LFMObject.Name+' has no type'
+CreateFootNote(VarPropContext),
LFMObject.NamePosition);
end;
VariableTypeName:=VarPropContext.Tool.ExtractDefinitionNodeType(VarPropContext.Node);
end else if (VarPropContext.Node.Desc=ctnProperty) then begin
TypeNode:=VarPropContext.Node;
VariableTypeName:=VarPropContext.Tool.ExtractPropType(TypeNode,false,false);
end else begin
LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
LFMObject.Name+' is not a variable'
+CreateFootNote(VarPropContext),
LFMObject.NamePosition);
exit;
end;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
fdfSearchInParentNodes,
fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
fdfIgnoreOverloadedProcs,fdfIgnoreCurContextNode];
Params.ContextNode:=TypeNode;
try
Result:=VarPropContext.Tool.FindBaseTypeOfNode(Params,TypeNode);
except
// ignore search/parse errors
on E: ECodeToolError do begin
{$IFDEF VerboseCheckLFM}
debugln(['FindClassNodeForLFMObject ',E.Message]);
{$ENDIF}
end;
end;
finally
Params.Free;
end;
if (Result.Node=nil) then begin
{$IFDEF VerboseCheckLFM}
debugln(['FindClassNodeForLFMObject FindBaseTypeOfNode failed. LFMNode=',LFMObject.GetPath,' ',FindContextToString(CreateFindContext(VarPropContext.Tool,TypeNode))]);
{$ENDIF}
LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
'class '+VariableTypeName+' not found'
+CreateFootNote(VarPropContext),
LFMObject.TypeNamePosition);
Result:=CleanFindContext;
exit;
end else if (not (Result.Node.Desc in AllClasses)) then begin
{$IFDEF VerboseCheckLFM}
debugln(['FindClassNodeForLFMObject base type is not a class. LFMNode=',LFMObject.GetPath,' ',FindContextToString(Result)]);
{$ENDIF}
LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
'class expected, but '+VariableTypeName+' found'
+CreateFootNote(VarPropContext),
LFMObject.TypeNamePosition);
Result:=CleanFindContext;
exit;
end;
// check classname
TypeName:=Result.Tool.ExtractClassName(Result.Node,false);
if not SameText(TypeName,LFMObject.TypeName) then begin
LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
TypeName+' expected, but '+LFMObject.TypeName+' found'
+CreateFootNote(VarPropContext),
LFMObject.TypeNamePosition);
exit;
end;
if LFMObject.TypeUnitName<>'' then begin
// lfm has explicit unitname
AnUnitName:=Result.Tool.GetSourceName(false);
if not SameText(AnUnitName,LFMObject.TypeUnitName) then begin
LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
AnUnitName+' expected, but '+LFMObject.TypeUnitName+' found'
+CreateFootNote(VarPropContext),
LFMObject.TypeUnitNamePosition);
exit;
end;
end;
end;
function FindClassContext(LFMObject: TLFMObjectNode): TFindContext;
var
Params: TFindDeclarationParams;
Identifier: PChar;
OldInput: TFindDeclarationInput;
StartTool: TStandardCodeTool;
aClassName: String;
begin
Result:=CleanFindContext;
aClassName:=LFMObject.TypeName;
if LFMObject.TypeUnitName<>'' then begin
// type with unitname
// -> search unit
try
Result.Tool:=FindCodeToolForUsedUnit(LFMObject.TypeUnitName,'',true);
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
if Result.Tool=nil then begin
LFMTree.AddError(lfmeUnitNotFound,LFMObject,
'unit '+LFMObject.TypeUnitName+' not found',LFMObject.TypeUnitNamePosition);
exit;
end;
// -> search class in unit interface
try
Result.Node:=Result.Tool.FindClassNodeInUnit(aClassName,true,false,true,false);
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
if (Result.Node=nil)
or (not (Result.Node.Desc in AllClasses)) then begin
Result.Tool:=nil;
LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
'type '+aClassName+' not found',LFMObject.TypeNamePosition);
exit;
end;
exit;
end;
// type without explicit unitname -> find declaration
Params:=TFindDeclarationParams.Create;
StartTool:=Self;
Identifier:=PChar(Pointer(aClassName));
try
Params.Flags:=[fdfExceptionOnNotFound,
fdfSearchInParentNodes,
fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
fdfIgnoreOverloadedProcs];
Params.ContextNode:=FindInterfaceNode;
if Params.ContextNode=nil then
Params.ContextNode:=FindMainUsesNode;
Params.SetIdentifier(StartTool,Identifier,nil);
try
Params.Save(OldInput);
if FindIdentifierInContext(Params) then begin
Params.Load(OldInput,true);
Result:=Params.NewCodeTool.FindBaseTypeOfNode(Params,Params.NewNode);
if (Result.Node=nil)
or (not (Result.Node.Desc in AllClasses)) then
Result:=CleanFindContext;
end;
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
if Result.Node=nil then begin
LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
'type '+aClassName+' not found',LFMObject.TypeNamePosition);
end;
finally
Params.Free;
end;
end;
procedure CheckLFMChildObject(LFMObject: TLFMObjectNode;
const ParentContext: TFindContext; ContextIsDefault: boolean);
var
LFMObjectName: String;
ChildContext: TFindContext;
ClassContext: TFindContext;
IdentifierFound: Boolean;
begin
// find variable for object
// find identifier in Lookup Root
LFMObjectName:=LFMObject.Name;
//DebugLn('CheckChildObject A LFMObjectName="',LFMObjectName,'"');
if LFMObjectName='' then begin
LFMTree.AddError(lfmeObjectNameMissing,LFMObject,'missing object name',
LFMObject.StartPos);
exit;
end;
ChildContext:=CleanFindContext;
IdentifierFound:=(not ContextIsDefault) and
FindLFMIdentifier(LFMObject,LFMObject.NamePosition,LFMObjectName,RootContext,
false,ObjectsMustExist,ChildContext);
//debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" IdentifierFound=',IdentifierFound,' ObjectsMustExist=',ObjectsMustExist,' ',FindContextToString(ChildContext)]);
if IdentifierFound and (ObjectsMustExist or (ChildContext.Node<>nil)) then
begin
if ChildContext.Node=nil then begin
// this is an extra entry, created via DefineProperties.
// this depends on the runtime class, so codetools cannot check it
exit;
end;
// find class node
//debugln(['CheckLFMChildObject searching class node: LFMObjectName="',LFMObjectName,'" ',FindContextToString(CreateFindContext(ChildContext.Tool,DefinitionNode))]);
ClassContext:=FindClassNodeForLFMObject(LFMObject,ChildContext);
//debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" class context: ',FindContextToString(ClassContext)]);
end else begin
// try the object type
ClassContext:=FindClassContext(LFMObject);
if ClassContext.Node=nil then exit;
end;
// check child LFM nodes
if ClassContext.Node<>nil then
CheckLFMObjectValues(LFMObject,ClassContext,false)
else
CheckLFMObjectValues(LFMObject,ParentContext,true);
end;
function FindClassNodeForPropertyType(LFMProperty: TLFMPropertyNode;
DefaultErrorPosition: integer; const PropertyContext: TFindContext): TFindContext;
var
Params: TFindDeclarationParams;
begin
Result:=CleanFindContext;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
fdfSearchInParentNodes,
fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
fdfIgnoreOverloadedProcs];
Params.ContextNode:=PropertyContext.Node;
Params.SetIdentifier(PropertyContext.Tool,nil,nil);
try
Result:=PropertyContext.Tool.FindBaseTypeOfNode(Params,
PropertyContext.Node);
except
// ignore search/parse errors
on E: ECodeToolError do ;
end;
finally
Params.Free;
end;
if Result.Node=nil then begin
LFMTree.AddError(lfmePropertyHasNoSubProperties,LFMProperty,
'property has no sub properties',
DefaultErrorPosition);
exit;
end;
end;
procedure CheckLFMProperty(LFMProperty: TLFMPropertyNode;
const ParentContext: TFindContext);
// checks properties. For example lines like 'OnShow = FormShow'
// or 'VertScrollBar.Range = 29'
// LFMProperty is the property node
// ParentContext is the context, where properties are searched.
// This can be a class or a property.
var
i: Integer;
CurName: string;
CurPropertyContext: TFindContext;
SearchContext: TFindContext;
begin
// find complete property name
//DebugLn('CheckLFMProperty A LFMProperty Name="',LFMProperty.CompleteName,'" ParentContext=',FindContextToString(ParentContext));
if LFMProperty.CompleteName='' then begin
LFMTree.AddError(lfmePropertyNameMissing,LFMProperty,
'property without name',LFMProperty.StartPos);
exit;
end;
// find every part of the property name
SearchContext:=ParentContext;
for i:=0 to LFMProperty.NameParts.Count-1 do begin
if SearchContext.Node.Desc=ctnProperty then begin
// get the type of the property and search the class node
SearchContext:=FindClassNodeForPropertyType(LFMProperty,
LFMProperty.NameParts.NamePositions[i],SearchContext);
if SearchContext.Node=nil then exit;
end;
CurName:=LFMProperty.NameParts.Names[i];
if not FindLFMIdentifier(LFMProperty,
LFMProperty.NameParts.NamePositions[i],
CurName,SearchContext,true,true,
CurPropertyContext)
then
break;
if CurPropertyContext.Node=nil then begin
// this is an extra entry, created via DefineProperties.
// There is no generic way to test such things
break;
end;
SearchContext:=CurPropertyContext;
end;
// ToDo: check value
end;
function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
const ClassContext: TFindContext; ContextIsDefault: boolean): boolean;
var
CurLFMNode: TLFMTreeNode;
begin
//DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues A ',LFMObject.Name,':',LFMObject.TypeName);
CurLFMNode:=LFMObject.FirstChild;
while CurLFMNode<>nil do begin
//DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues B ',CurLFMNode.ClassName);
case CurLFMNode.TheType of
lfmnObject:
CheckLFMChildObject(TLFMObjectNode(CurLFMNode),ClassContext,ContextIsDefault);
lfmnProperty:
if not ContextIsDefault then
CheckLFMProperty(TLFMPropertyNode(CurLFMNode),ClassContext);
end;
CurLFMNode:=CurLFMNode.NextSibling;
end;
Result:=true;
end;
function CheckLFMRoot(RootLFMNode: TLFMTreeNode): boolean;
var
LookupRootLFMNode: TLFMObjectNode;
LookupRootTypeName, LookupRootTypeUnitName, CurUnitName: String;
RootClassNode: TCodeTreeNode;
begin
Result:=false;
//DebugLn('TStandardCodeTool.CheckLFM.CheckLFMRoot checking root ...');
// get root object node
if (RootLFMNode=nil) or (not (RootLFMNode is TLFMObjectNode)) then begin
LFMTree.AddError(lfmeMissingRoot,nil,'missing root object',1);
exit;
end;
LookupRootLFMNode:=TLFMObjectNode(RootLFMNode);
// get type name of root object
LookupRootTypeName:=LookupRootLFMNode.TypeName;
if LookupRootTypeName='' then begin
LFMTree.AddError(lfmeMissingRoot,nil,'missing type of root object',1);
exit;
end;
// find root type
LookupRootTypeUnitName:=LookupRootLFMNode.TypeUnitName;
if LookupRootTypeUnitName<>'' then begin
CurUnitName:=GetSourceName(false);
if SameText(CurUnitName,LookupRootTypeUnitName) then begin
// unitname fits
end else if RootMustBeClassInIntf or RootMustBeClassInUnit then begin
LFMTree.AddError(lfmeMissingRoot,LookupRootLFMNode,
'unitname '+LookupRootLFMNode.TypeUnitName+' mismatch',
LookupRootLFMNode.TypeUnitNamePosition);
exit;
end;
end;
if RootMustBeClassInIntf then begin
RootClassNode:=FindClassNodeInInterface(LookupRootTypeName,true,false,false);
RootContext:=CleanFindContext;
RootContext.Node:=RootClassNode;
RootContext.Tool:=Self;
end else if RootMustBeClassInUnit then begin
RootClassNode:=FindClassNodeInUnit(LookupRootTypeName,true,false,false,false);
RootContext:=CleanFindContext;
RootContext.Node:=RootClassNode;
RootContext.Tool:=Self;
end else begin
RootContext:=FindClassContext(LookupRootLFMNode);
RootClassNode:=RootContext.Node;
if RootClassNode=nil then exit;
end;
if RootClassNode=nil then begin
LFMTree.AddError(lfmeMissingRoot,LookupRootLFMNode,
'type '+LookupRootLFMNode.TypeName+' not found',
LookupRootLFMNode.TypeNamePosition);
exit;
end;
Result:=CheckLFMObjectValues(LookupRootLFMNode,RootContext,false);
end;
var
CurRootLFMNode: TLFMTreeNode;
begin
Result:=false;
//DebugLn('TStandardCodeTool.CheckLFM A');
// create tree from LFM file
LFMTree:=DefaultLFMTrees.GetLFMTree(LFMBuf,true);
ActivateGlobalWriteLock;
try
//DebugLn('TStandardCodeTool.CheckLFM parsing LFM ...');
if not LFMTree.ParseIfNeeded then exit;
// parse unit and find LookupRoot
//DebugLn('TStandardCodeTool.CheckLFM parsing unit ...');
BuildTree(lsrImplementationUsesSectionEnd);
// find every identifier
//DebugLn('TStandardCodeTool.CheckLFM checking identifiers ...');
CurRootLFMNode:=LFMTree.Root;
while CurRootLFMNode<>nil do begin
if not CheckLFMRoot(CurRootLFMNode) then exit;
CurRootLFMNode:=CurRootLFMNode.NextSibling;
end;
finally
DeactivateGlobalWriteLock;
end;
Result:=LFMTree.FirstError=nil;
end;
function TStandardCodeTool.FindCreateFormStatement(StartPos: integer;
const AClassName, AVarName: string;
out Position: TAtomPosition): integer;
// 0=found, -1=not found, 1=found, but wrong classname
var MainBeginNode: TCodeTreeNode;
ClassNameFits: boolean;
begin
Result:=-1;
Position.StartPos:=-1;
if (AClassName='') or (AVarName='') or (length(AClassName)>255)
or (length(AVarName)>255) then exit;
if StartPos<1 then begin
BuildTree(lsrEnd);
MainBeginNode:=FindMainBeginEndNode;
if MainBeginNode=nil then exit;
StartPos:=MainBeginNode.StartPos;
if StartPos<1 then exit;
end;
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
if UpAtomIs('APPLICATION') then begin
Position.StartPos:=CurPos.StartPos;
if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('CREATEFORM')
and ReadNextAtomIsChar('(') then begin
ReadNextAtom;
ClassNameFits:=AtomIsIdentifier(PChar(AClassName));
if ReadNextAtomIsChar(',')
and (ReadNextAtomIsIdentifier(PChar(AVarName)) or (AVarName='*')) then begin
if ReadNextAtomIsChar(')') then ReadNextAtomIsChar(';');
Position.EndPos:=CurPos.EndPos;
if ClassNameFits then
Result:=0
else
Result:=1;
exit;
end;
end;
end;
until (CurPos.StartPos>SrcLen);
Result:=-1;
end;
function TStandardCodeTool.AddCreateFormStatement(const AClassName,
AVarName: string; SourceChangeCache: TSourceChangeCache): boolean;
var MainBeginNode: TCodeTreeNode;
OldPosition: TAtomPosition;
FromPos, ToPos, Indent: integer;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if (AClassName='') or (length(AClassName)>255) or (AVarName='')
or (length(AVarName)>255) then exit;
BuildTree(lsrEnd);
MainBeginNode:=FindMainBeginEndNode;
if MainBeginNode=nil then exit;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
FromPos:=-1;
if FindCreateFormStatement(MainBeginNode.StartPos,AClassName,
AVarName,OldPosition)=-1
then begin
// does not exist -> create as last in front of 'Application.Run'
MoveCursorToCleanPos(MainBeginNode.StartPos);
repeat
if ReadNextUpAtomIs('APPLICATION') then begin
FromPos:=CurPos.StartPos;
if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
break;
end;
FromPos:=-1;
end;
until (CurPos.StartPos>SrcLen);
if FromPos<1 then exit;
SourceChangeCache.MainScanner:=Scanner;
Indent:=Beauty.GetLineIndent(Src,FromPos);
FromPos:=FindLineEndOrCodeInFrontOfPosition(FromPos);
SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,FromPos,
Beauty.BeautifyStatement(
'Application.CreateForm('+AClassName+','+AVarName+');',Indent));
end else begin
// it exists -> replace it
FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
SourceChangeCache.MainScanner:=Scanner;
SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
Beauty.BeautifyStatement(
'Application.CreateForm('+AClassName+','+AVarName+');',
Beauty.Indent));
end;
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.RemoveCreateFormStatement(const AVarName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var Position: TAtomPosition;
FromPos, ToPos: integer;
begin
Result:=false;
if FindCreateFormStatement(-1,'*',AVarName,Position)=-1 then
exit;
FromPos:=FindLineEndOrCodeInFrontOfPosition(Position.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(Position.EndPos);
SourceChangeCache.MainScanner:=Scanner;
SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.ChangeCreateFormStatement(StartPos: integer;
const OldClassName, OldVarName: string;
const NewClassName, NewVarName: string;
OnlyIfExists: boolean; SourceChangeCache: TSourceChangeCache): boolean;
var MainBeginNode: TCodeTreeNode;
OldPosition: TAtomPosition;
FromPos, ToPos: integer;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if (OldClassName='') or (length(OldClassName)>255)
or (OldVarName='') or (length(OldVarName)>255)
or (NewClassName='') or (length(NewClassName)>255)
or (NewVarName='') or (length(NewVarName)>255)
then exit;
BuildTree(lsrEnd);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
if StartPos<1 then begin
MainBeginNode:=FindMainBeginEndNode;
if MainBeginNode=nil then exit;
StartPos:=MainBeginNode.StartPos;
if StartPos<1 then exit;
end;
FromPos:=-1;
if FindCreateFormStatement(StartPos,OldClassName,
OldVarName,OldPosition)=-1 then begin
// does not exist
if OnlyIfExists then begin
Result:=true;
exit;
end;
Result:=AddCreateFormStatement(NewClassName,NewVarName,SourceChangeCache);
end else begin
// replace
FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
SourceChangeCache.MainScanner:=Scanner;
SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
Beauty.BeautifyStatement(
'Application.CreateForm('+NewClassName+','+NewVarName+');',
Beauty.Indent));
Result:=SourceChangeCache.Apply;
end;
end;
function TStandardCodeTool.ListAllCreateFormStatements: TStrings;
// list format: VarName:ClassName
var Position: integer;
StatementPos: TAtomPosition;
s:string;
var MainBeginNode: TCodeTreeNode;
begin
BuildTree(lsrEnd);
Result:=TStringList.Create;
MainBeginNode:=FindMainBeginEndNode;
if MainBeginNode=nil then exit;
Position:=MainBeginNode.StartPos;
repeat
if FindCreateFormStatement(Position,'*','*',StatementPos)=-1 then
exit;
Position:=StatementPos.EndPos;
MoveCursorToCleanPos(StatementPos.StartPos);
ReadNextAtom; // read 'Application'
ReadNextAtom; // read '.'
ReadNextAtom; // read 'CreateForm'
ReadNextAtom; // read '('
ReadNextAtom; // read class name
s:=GetAtom;
ReadNextAtom; // read ','
ReadNextAtom; // read variable name
s:=GetAtom+':'+s;
Result.Add(s);
until false;
end;
function TStandardCodeTool.SetAllCreateFromStatements(List: TStrings;
SourceChangeCache: TSourceChangeCache): boolean;
{ every string in the list has the format VarName:ClassName
or simply VarName In the latter case it will be automatically expanded
to VarName:TVarName
}
var Position, InsertPos, i, ColonPos, Indent: integer;
StatementPos: TAtomPosition;
MainBeginNode: TCodeTreeNode;
AClassName, AVarName: string;
LastEndPos: Integer;
Beauty: TBeautifyCodeOptions;
begin
Result:= false;
if (List = nil) or (SourceChangeCache = nil) then exit;
BuildTree(lsrEnd);
{ first delete all CreateForm Statements }
SourceChangeCache.MainScanner:= Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
MainBeginNode:=FindMainBeginEndNode;
if MainBeginNode = nil then exit;
Position:=MainBeginNode.StartPos;
InsertPos:=-1;
LastEndPos:=-1;
repeat
if FindCreateFormStatement(Position, '*', '*', StatementPos) = -1 then break;
Position:=StatementPos.EndPos;
StatementPos.StartPos:=FindLineEndOrCodeInFrontOfPosition(StatementPos.StartPos);
if (LastEndPos>0) and (StatementPos.StartPos<LastEndPos) then
StatementPos.StartPos:=LastEndPos;
if InsertPos < 1 then InsertPos:= StatementPos.StartPos;
StatementPos.EndPos:=FindLineEndOrCodeAfterPosition(StatementPos.EndPos);
LastEndPos:=StatementPos.EndPos;
if not SourceChangeCache.Replace(gtNone,gtNone, StatementPos.StartPos, StatementPos.EndPos, '') then
exit;
until false;
Result:=SourceChangeCache.Apply;
if not Result then exit;
{ then add all CreateForm Statements }
if InsertPos < 1 then begin
{ there was no createform statement -> insert in front of Application.Run }
MoveCursorToCleanPos(MainBeginNode.StartPos);
repeat
if ReadNextUpAtomIs('APPLICATION') then begin
InsertPos:=CurPos.StartPos;
if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertPos);
break;
end;
InsertPos:=-1;
end;
until (CurPos.StartPos>SrcLen);
if InsertPos < 1 then exit;
end;
for i:= 0 to List.Count - 1 do begin
if Length(List[i]) <= 1 then continue;
ColonPos:= Pos(List[i], ':');
if (ColonPos > 1) then begin
AVarName:= Copy(List[i], 1, ColonPos);
AClassName:= Copy(List[i], ColonPos + 1, Length(List[i]) - ColonPos);
end else begin
AVarName:= List[i];
AClassName:= 'T' + AVarName;
end;
Indent:=Beauty.GetLineIndent(Src, InsertPos);
SourceChangeCache.Replace(gtNewLine, gtNewLine, InsertPos, InsertPos,
SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
'Application.CreateForm('+AClassName+','+AVarName+');', Indent));
end;
Result:= Result and SourceChangeCache.Apply;
end;
function TStandardCodeTool.SetApplicationScaledStatement(
const NewScaled: boolean; SourceChangeCache: TSourceChangeCache): boolean;
begin
Result := SetApplicationStatement('Scaled', BoolToStr(NewScaled, True), SourceChangeCache);
end;
function TStandardCodeTool.SetApplicationStatement(const APropertyName,
NewCode: string; SourceChangeCache: TSourceChangeCache): boolean;
var
StartPos, ConstStartPos, EndPos: integer;
NewStatement: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// search old Application.APropertyName:=XYZ statement
FindApplicationStatement(UpperCase(APropertyName),StartPos,ConstStartPos,EndPos);
// create statement. FindApplicationStatement always returns an insert point.
NewStatement:='Application.'+APropertyName+':='+NewCode+';';
NewStatement:=Beauty.BeautifyStatement(NewStatement,Beauty.Indent);
SourceChangeCache.MainScanner:=Scanner;
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,StartPos,EndPos,NewStatement) then
exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.FindApplicationTitleStatement(out StartPos,
StringConstStartPos, EndPos: integer): boolean;
begin
Result := FindApplicationStatement('TITLE', StartPos, StringConstStartPos, EndPos);
end;
function TStandardCodeTool.GetApplicationTitleStatement(StringConstStartPos,
EndPos: integer; var Title: string): boolean;
var
FormatStringParams: string;
begin
Result:=false;
Title:='';
if (StringConstStartPos<1) or (StringConstStartPos>SrcLen) then exit;
MoveCursorToCleanPos(StringConstStartPos);
ReadNextAtom;
if not AtomIsStringConstant then exit;
Result:=GetStringConstAsFormatString(StringConstStartPos,EndPos,Title,
FormatStringParams);
if FormatStringParams='' then ;
end;
function TStandardCodeTool.SetApplicationTitleStatement(const NewTitle: string;
SourceChangeCache: TSourceChangeCache): boolean;
begin
Result := SetApplicationStatement('Title', StringToPascalConst(NewTitle), SourceChangeCache);
end;
function TStandardCodeTool.RemoveApplicationTitleStatement(
SourceChangeCache: TSourceChangeCache): boolean;
begin
Result := RemoveApplicationStatement('TITLE', SourceChangeCache);
end;
function TStandardCodeTool.RenameForm(const OldFormName,
OldFormClassName: string; const NewFormName, NewFormClassName: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
IdentList: TStringList;
begin
Result:=false;
if (OldFormName='') or (OldFormClassName='')
or (NewFormName='') or (NewFormClassName='')
or (SourceChangeCache=nil) then exit;
if (OldFormName=NewFormName)
and (OldFormClassName=NewFormClassName) then exit;
IdentList:=TStringList.Create;
try
if (OldFormName<>NewFormName) then begin
IdentList.Add(OldFormName);
IdentList.Add(NewFormName);
end;
if (OldFormClassName<>NewFormClassName) then begin
IdentList.Add(OldFormClassName);
IdentList.Add(NewFormClassName);
end;
Result:=ReplaceWords(IdentList,false,SourceChangeCache);
finally
IdentList.Free;
end;
end;
function TStandardCodeTool.FindFormAncestor(const AClassName: string;
out AncestorClassName: string): boolean;
var
ClassNode: TCodeTreeNode;
begin
Result:=false;
AncestorClassName:='';
if AClassName='' then exit;
BuildTree(lsrImplementationStart);
ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
if (ClassNode=nil) then exit;
// search the ancestor name
MoveCursorToNodeStart(ClassNode);
ReadNextAtom; // read keyword 'class', 'object', 'interface', 'dispinterface'
while UpAtomIs('SEALED') or UpAtomIs('ABSTRACT') do ReadNextAtom;
if UpAtomIs('PACKED') or UpAtomIs('BITPACKED') then ReadNextAtom;
ReadNextAtom;
if AtomIsChar('(') then begin
ReadNextAtom;
if UpAtomIs('SPECIALIZE') then
ReadNextAtom;
if AtomIsIdentifier then
AncestorClassName:=GetAtom;
end;
if AncestorClassName='' then
AncestorClassName:='TObject';
Result:=true;
end;
{-------------------------------------------------------------------------------
function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache): boolean;
Search in all used sources (not only the cleaned source) for identifiers.
It will find all identifiers, except identifiers in compiler directives.
This includes identifiers in string constants and comments.
ChangeStrings = true, means to replace in string constants too
-------------------------------------------------------------------------------}
function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
SkipPointWords: boolean): boolean;
function CheckIdentifier(const CurSource: string;
IdentStart: integer): boolean;
var
p: integer;
begin
if not SkipPointWords then exit(true);
p:=IdentStart-1;
while (p>0) and (IsSpaceChar[CurSource[p]]) do dec(p);
Result:=(p<1) or (CurSource[p]<>'.');
end;
procedure ReplaceWordsInSource(ACode: TCodeBuffer);
var
StartPos, EndPos, MaxPos, IdentStart, IdentEnd: integer;
CurSource: string;
i: integer;
begin
CurSource:=ACode.Source;
MaxPos:=length(CurSource);
StartPos:=1;
// go through all source parts between compiler directives
//DebugLn('TStandardCodeTool.ReplaceWords ',ACode.Filename);
repeat
EndPos:=FindNextCompilerDirective(CurSource,StartPos,
Scanner.NestedComments);
if EndPos>MaxPos then EndPos:=MaxPos+1;
// search all identifiers
repeat
if ChangeStrings then
IdentStart:=FindNextIdentifier(CurSource,StartPos,EndPos-1)
else
IdentStart:=FindNextIdentifierSkipStrings(CurSource,StartPos,EndPos-1);
if IdentStart>=EndPos then
break;
i:=0;
while i<IdentList.Count do begin
if (IdentList[i]<>'')
and (BasicCodeTools.CompareIdentifiers(PChar(Pointer(IdentList[i])),
@CurSource[IdentStart])=0)
and CheckIdentifier(CurSource,IdentStart)
and (IdentList[i]<>IdentList[i+1])
then begin
// identifier found -> replace
IdentEnd:=IdentStart+length(IdentList[i]);
//DebugLn('TStandardCodeTool.ReplaceWords replacing: ',
//' "',copy(CurSource,IdentStart,IdentEnd-IdentStart),'" -> "',IdentList[i+1],'" at ',IdentStart
//);
SourceChangeCache.ReplaceEx(gtNone,gtNone,1,1,
ACode,IdentStart,IdentEnd,IdentList[i+1]);
break;
end;
inc(i,2);
end;
// skip identifier
StartPos:=IdentStart;
while (StartPos<MaxPos) and IsIdentChar[CurSource[StartPos]] do
inc(StartPos);
until false;
if EndPos<=MaxPos then begin
// skip comment
StartPos:=FindCommentEnd(CurSource,EndPos,Scanner.NestedComments);
if StartPos>MaxPos then break;
end else begin
break;
end;
until false;
end;
var
SourceList: TFPList;
i: integer;
begin
Result:=false;
if (IdentList=nil) or (IdentList.Count=0) or (SourceChangeCache=nil)
or (Odd(IdentList.Count)) then exit;
BuildTree(lsrEnd);
if Scanner=nil then exit;
SourceChangeCache.MainScanner:=Scanner;
SourceList:=TFPList.Create;
try
Scanner.FindCodeInRange(1,SrcLen,SourceList);
for i:=0 to SourceList.Count-1 do begin
ReplaceWordsInSource(TCodeBuffer(SourceList[i]));
end;
finally
SourceList.Free;
end;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.FindNearestIdentifierNode(
const CursorPos: TCodeXYPosition; IdentTree: TAVLTree): TAVLTreeNode;
var
CleanCursorPos: integer;
BestDiff: Integer;
CurIdentNode: TAVLTreeNode;
CurDiff: Integer;
begin
Result:=nil;
if IdentTree=nil then exit;
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,[]);
BestDiff:=SrcLen+1;
MoveCursorToCleanPos(1);
repeat
ReadNextAtom;
if AtomIsIdentifier then begin
CurIdentNode:=
IdentTree.FindKey(@Src[CurPos.StartPos],
TListSortCompare(@CompareIdentifiers));
if CurIdentNode<>nil then begin
CurDiff:=CurPos.StartPos-CleanCursorPos;
if CurDiff<0 then CurDiff:=-CurDiff;
if (Result=nil) or (CurDiff<BestDiff) then begin
BestDiff:=CurDiff;
Result:=CurIdentNode;
end;
end;
end;
until CurPos.EndPos>SrcLen;
end;
function TStandardCodeTool.ReplaceWord(const OldWord, NewWord: string;
ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
SkipPointWords: boolean): boolean;
var
IdentList: TStringList;
begin
Result:=false;
if OldWord='' then exit;
if OldWord=NewWord then exit(true);
if (SourceChangeCache=nil) then exit;
IdentList:=TStringList.Create;
try
IdentList.Add(OldWord);
IdentList.Add(NewWord);
Result:=ReplaceWords(IdentList,ChangeStrings,SourceChangeCache,SkipPointWords);
finally
IdentList.Free;
end;
end;
function TStandardCodeTool.CommentCode(const StartPos, EndPos: integer;
SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;
var
i: LongInt;
CurStartPos: LongInt;
CommentNeeded: Boolean;
CurEndPos: LongInt;
begin
if StartPos>=EndPos then
RaiseException(20170421201123,'TStandardCodeTool CommentCode');
Result:=false;
// comment with curly brackets {}
i:=StartPos;
CurStartPos:=i;
CurEndPos:=CurStartPos;
CommentNeeded:=false;
repeat
//debugln(['TPascalReaderTool.CommentCode ',dbgstr(Src[i]),' Needed=',CommentNeeded,' ',dbgstr(copy(Src,CurStartPos,CurEndPos-CurStartPos))]);
if (Src[i]='{') or (i>=EndPos) then begin
// the area contains a comment -> comment in front
if CommentNeeded then begin
if not SourceChangeCache.Replace(gtNone,gtNone,
CurStartPos,CurStartPos,'{') then exit;
if not SourceChangeCache.Replace(gtNone,gtNone,
CurEndPos,CurEndPos,'}') then exit;
//DebugLn('Comment "',copy(Src,CurStartPos,i-CurStartPos),'"');
CommentNeeded:=false;
end;
if i>=EndPos then break;
// skip comment
i:=FindCommentEnd(Src,i,Scanner.NestedComments)-1;
end else if not IsSpaceChar[Src[i]] then begin
if not CommentNeeded then begin
CurStartPos:=i;
CommentNeeded:=true;
end;
CurEndPos:=i+1;
end;
inc(i);
until false;
if Apply then
Result:=SourceChangeCache.Apply
else
Result:=true;
end;
function TStandardCodeTool.GetStringConstBounds(
const CursorPos: TCodeXYPosition;
out StartPos, EndPos: TCodeXYPosition; ResolveComments: boolean): boolean;
// examples:
// 's1'+'s2'#13+AFunction(...)+inherited AMethod
{ $DEFINE VerboseGetStringConstBounds}
type
TStrConstTokenType = (scatNone, scatStrConst, scatPlus, scatIdent,
scatInherited, scatPoint, scatUp,
scatEdgedBracketOpen, scatEdgedBracketClose,
scatRoundBracketOpen, scatRoundBracketClose);
{$IFDEF VerboseGetStringConstBounds}
function EnumToStr(TokenType: TStrConstTokenType): string;
begin
WriteStr(Result, TokenType);
end;
{$ENDIF}
function GetCurrentTokenType: TStrConstTokenType;
begin
if (CurPos.StartPos<1) or (CurPos.StartPos>SrcLen) then
Result:=scatNone
else if AtomIsStringConstant then
Result:=scatStrConst
else if AtomIsChar('+') then
Result:=scatPlus
else if AtomIsIdentifier then
Result:=scatIdent
else if UpAtomIs('INHERITED') then
Result:=scatInherited
else if CurPos.Flag=cafPoint then
Result:=scatPoint
else if AtomIsChar('^') then
Result:=scatUp
else if CurPos.Flag=cafRoundBracketOpen then
Result:=scatRoundBracketOpen
else if CurPos.Flag=cafRoundBracketClose then
Result:=scatRoundBracketClose
else if CurPos.Flag=cafEdgedBracketOpen then
Result:=scatEdgedBracketOpen
else if CurPos.Flag=cafEdgedBracketClose then
Result:=scatEdgedBracketClose
else
Result:=scatNone;
end;
var
CleanCursorPos: integer;
SameArea: TAtomPosition;
LastToken, CurrentToken: TStrConstTokenType;
StartCleanPos, EndCleanPos: integer;
StringConstantFound: Boolean;
begin
StartPos:=CursorPos;
EndPos:=CursorPos;
Result:=true;
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds A Start at ',CleanPosToStr(CleanCursorPos),' "',copy(Src,CleanCursorPos-5,5),'" | "',copy(Src,CleanCursorPos,5),'"');
{$ENDIF}
GetCleanPosInfo(-1,CleanCursorPos,ResolveComments,SameArea);
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds B Same Area: ',CleanPosToStr(SameArea.StartPos),'-',CleanPosToStr(SameArea.EndPos),' "',copy(Src,SameArea.StartPos,SameArea.EndPos-SameArea.StartPos),'"');
{$ENDIF}
if (SameArea.EndPos=SameArea.StartPos) or (SameArea.StartPos>SrcLen) then
exit;
// read til end of string constant
MoveCursorToCleanPos(SameArea.StartPos);
ReadNextAtom;
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds read til end of string Atom=',GetAtom);
{$ENDIF}
CurrentToken:=GetCurrentTokenType;
if (CurrentToken=scatNone) then exit;
StringConstantFound:=(CurrentToken=scatStrConst);
repeat
EndCleanPos:=CurPos.EndPos;
ReadNextAtom;
LastToken:=CurrentToken;
CurrentToken:=GetCurrentTokenType;
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds Read Forward: ',GetAtom,' EndCleanPos=',dbgs(EndCleanPos),
' LastToken=',EnumToStr(LastToken),
' CurrentToken=',EnumToStr(CurrentToken),' ',EnumToStr(GetCurrentTokenType));
{$ENDIF}
case CurrentToken of
scatNone, scatEdgedBracketClose, scatRoundBracketClose:
if not (LastToken in [scatStrConst,scatIdent,scatUp,
scatEdgedBracketClose, scatRoundBracketClose])
then
exit
else
break;
scatStrConst:
if not (LastToken in [scatPlus]) then
exit
else
StringConstantFound:=true;
scatPlus:
if not (LastToken in [scatStrConst, scatIdent, scatUp,
scatEdgedBracketClose, scatRoundBracketClose]) then exit;
scatIdent:
if not (LastToken in [scatPlus, scatPoint, scatInherited]) then exit;
scatInherited:
if not (LastToken in [scatPlus, scatPoint]) then exit;
scatPoint:
if not (LastToken in [scatIdent, scatUp, scatRoundBracketClose,
scatEdgedBracketClose]) then
exit;
scatEdgedBracketOpen,scatRoundBracketOpen:
if not (LastToken in [scatIdent, scatUp]) then
exit
else begin
ReadTilBracketClose(true);
CurrentToken:=GetCurrentTokenType;
end;
end;
until false;
// read til start of string constant
MoveCursorToCleanPos(SameArea.StartPos);
ReadNextAtom;
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds Read til start of string ',GetAtom);
{$ENDIF}
CurrentToken:=GetCurrentTokenType;
repeat
StartCleanPos:=CurPos.StartPos;
ReadPriorAtom;
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds Read backward: ',GetAtom,' StartCleanPos=',dbgs(StartCleanPos));
{$ENDIF}
LastToken:=CurrentToken;
CurrentToken:=GetCurrentTokenType;
case CurrentToken of
scatNone, scatEdgedBracketOpen, scatRoundBracketOpen:
if not (LastToken in [scatStrConst,scatIdent,scatPlus]) then
exit
else
break;
scatStrConst:
if not (LastToken in [scatPlus]) then
exit
else
StringConstantFound:=true;
scatPlus:
if not (LastToken in [scatStrConst, scatIdent, scatRoundBracketOpen]) then
exit;
scatIdent:
if not (LastToken in [scatPlus, scatPoint, scatUp, scatRoundBracketOpen,
scatEdgedBracketOpen]) then exit;
scatInherited:
if not (LastToken in [scatIdent]) then exit;
scatPoint:
if not (LastToken in [scatIdent]) then exit;
scatEdgedBracketClose,scatRoundBracketClose:
if not (LastToken in [scatPlus, scatUp, scatPoint]) then
exit
else begin
ReadBackTilBracketOpen(true);
CurrentToken:=GetCurrentTokenType;
end;
end;
until false;
// convert start and end position
{$IFDEF VerboseGetStringConstBounds}
DebugLn('TStandardCodeTool.GetStringConstBounds END "',copy(Src,StartCleanPos,EndCleanPos-StartCleanPos),'" StringConstantFound=',dbgs(StringConstantFound));
{$ENDIF}
if not StringConstantFound then begin
EndCleanPos:=StartCleanPos;
end;
if not CleanPosToCaret(StartCleanPos,StartPos) then exit;
if not CleanPosToCaret(EndCleanPos,EndPos) then exit;
Result:=true;
end;
function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
EndPos: integer; out FormatStringConstant, FormatParameters: string;
out StartInStringConst, EndInStringConst: boolean): boolean;
{ Converts a string constant into the parameters for a Format call of the
system unit.
Examples:
'Hallo' -> "Hallo", ""
'A'+IntToStr(1) -> "A%s", "IntToStr(1)"
'A%B'#13#10 -> "A%sB%s", "'%', #13#10"
}
procedure AddChar(c: char);
begin
FormatStringConstant:=FormatStringConstant+c;
end;
procedure AddParameter(const NewParam: string);
begin
FormatStringConstant:=FormatStringConstant+'%s';
if FormatParameters<>'' then
FormatParameters:=FormatParameters+',';
FormatParameters:=FormatParameters+NewParam;
end;
procedure AddParameter(ParamStartPos,ParamEndPos: integer);
begin
AddParameter(copy(Src,ParamStartPos,ParamEndPos-ParamStartPos));
end;
procedure ConvertStringConstant;
var
APos: Integer;
CharConstStart: Integer;
InRange: Boolean;
begin
if (CurPos.StartPos<StartPos) and (CurPos.EndPos>StartPos) then
StartInStringConst:=true;
if (CurPos.StartPos<EndPos) and (CurPos.EndPos>EndPos) then
EndInStringConst:=true;
APos:=CurPos.StartPos;
while APos<EndPos do begin
InRange:=(APos>=StartPos);
//debugln('ConvertStringConstant InRange=',dbgs(InRange),' Src[APos]=',Src[APos]);
if Src[APos]='''' then begin
// read string constant
inc(APos);
while APos<EndPos do begin
InRange:=(APos>=StartPos);
case Src[APos] of
'''':
if (APos<EndPos-1) and (Src[APos+1]='''') then begin
// a double ' means a single '
if InRange then begin
AddChar('''');
AddChar('''');
end;
inc(APos,2);
end else begin
// a single ' means end of string constant
inc(APos);
break;
end;
else
begin
// normal char
if InRange then
AddChar(Src[APos]);
inc(APos);
end;
end;
end;
end else if Src[APos]='#' then begin
CharConstStart:=APos;
InRange:=(APos+1>=StartPos);
repeat
// read char constant
inc(APos);
if APos<EndPos then begin
if Src[APos-1]='#' then begin
if IsNumberChar[Src[APos]] then begin
// read decimal number
while (APos<EndPos) and IsNumberChar[Src[APos]] do
inc(APos);
end else if Src[APos]='$' then begin
// read hexnumber
while (APos<EndPos) and IsHexNumberChar[Src[APos]] do
inc(APos);
end;
end;
end;
until (APos>=EndPos) or (Src[APos]<>'#');
if InRange then
AddParameter(CharConstStart,APos);
end else
break;
end;
end;
procedure ConvertOther;
var
ParamStartPos: Integer;
ParamEndPos: Integer;
begin
// read till next string constant
ParamStartPos:=CurPos.StartPos;
ParamEndPos:=ParamStartPos;
while (not AtomIsStringConstant) and (CurPos.EndPos<=EndPos) do begin
if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
ReadTilBracketClose(true);
if not AtomIsChar('+') then ParamEndPos:=CurPos.EndPos;
ReadNextAtom;
end;
if ParamEndPos>ParamStartPos then
AddParameter(ParamStartPos,ParamEndPos);
if AtomIsStringConstant then UndoReadNextAtom;
end;
var
ANode: TCodeTreeNode;
CodePosInFront: LongInt;
begin
Result:=false;
// read string constants and convert it
FormatStringConstant:='';
FormatParameters:='';
StartInStringConst:=false;
EndInStringConst:=false;
ANode:=FindDeepestNodeAtPos(StartPos,True);
CodePosInFront:=ANode.StartPos;
MoveCursorToCleanPos(CodePosInFront);
if EndPos>SrcLen then EndPos:=SrcLen+1;
repeat
ReadNextAtom;
//debugln('GetStringConstAsFormatString Atom=',GetAtom);
if (CurPos.StartPos>=EndPos) then break;
if CurPos.EndPos>StartPos then begin
//debugln('GetStringConstAsFormatString Parsing...');
if AtomIsStringConstant then begin
// a string constant
ConvertStringConstant;
end else if AtomIsChar('+') then begin
// simply ignore
end else if (CurPos.Flag=cafRoundBracketOpen) or AtomIsIdentifier
then begin
// add as parameter
ConvertOther;
end else
// string constant end
break;
end;
until false;
Result:=FormatStringConstant<>'';
end;
function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
EndPos: integer; out FormatStringConstant, FormatParameters: string
): boolean;
var
StartInStringConst, EndInStringConstant: boolean;
begin
Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
FormatParameters,StartInStringConst,EndInStringConstant);
if StartInStringConst then ;
if EndInStringConstant then ;
end;
function TStandardCodeTool.ExtractOperand(const CursorPos: TCodeXYPosition; out
Operand: string; WithPostTokens, WithAsOperator,
WithoutTrailingPoints: boolean): boolean;
var
CleanPos: integer;
StartPos: LongInt;
EndPos: LongInt;
Node: TCodeTreeNode;
begin
Result:=false;
Operand:='';
if CursorPos.Code.LineColIsSpace(CursorPos.Y,CursorPos.X) then exit;
BuildTreeAndGetCleanPos(CursorPos,CleanPos);
Node:=FindDeepestNodeAtPos(CleanPos,true);
StartPos:=FindStartOfTerm(CleanPos,NodeTermInType(Node));
if StartPos<1 then exit;
StartPos:=FindNextNonSpace(Src,StartPos);
if StartPos>CleanPos then exit;
EndPos:=FindEndOfTerm(CleanPos,false,WithAsOperator);
if not WithPostTokens then begin
MoveCursorToCleanPos(CleanPos);
ReadNextAtom;
if CurPos.EndPos<EndPos then
EndPos:=CurPos.EndPos;
end;
if EndPos<1 then exit;
//DebugLn(['TStandardCodeTool.ExtractOperand "',dbgstr(copy(Src,StartPos,EndPos-StartPos)),'"']);
Operand:=ExtractCode(StartPos,EndPos,[phpCommentsToSpace]);
if WithoutTrailingPoints then begin
while (Operand<>'') and (Operand[length(Operand)]='.') do
Operand:=copy(Operand,1,length(Operand)-1);
end;
Result:=true;
end;
function TStandardCodeTool.FindApplicationScaledStatement(out StartPos,
BooleanConstStartPos, EndPos: integer): boolean;
begin
Result := FindApplicationStatement('SCALED', StartPos, BooleanConstStartPos, EndPos);
end;
function TStandardCodeTool.FindApplicationStatement(const APropertyUpCase: string;
out StartPos, ConstStartPos, EndPos: integer): boolean;
// Find statement "Application.APropertyUpCase:=XYZ;" and return True if found.
// Also return its positions (Start, const "XYZ" and End) in out parameters.
// If not found, out parameters get a good position to insert such a statement.
var
MainBeginNode: TCodeTreeNode;
AppPos, FirstAppPos: Integer;
begin
Result:=false;
StartPos:=-1;
ConstStartPos:=-1;
EndPos:=-1;
FirstAppPos:=-1;
BuildTree(lsrEnd);
MainBeginNode:=FindMainBeginEndNode;
if (MainBeginNode=nil) or (MainBeginNode.StartPos<1) then exit;
MoveCursorToCleanPos(MainBeginNode.StartPos);
repeat
ReadNextAtom;
if UpAtomIs('APPLICATION') then
begin
AppPos:=CurPos.StartPos;
if FirstAppPos=-1 then
FirstAppPos:=AppPos;
ReadNextAtom;
if AtomIsChar('.') then
begin // Application.APropertyUpCase:=XYZ;
if ReadNextUpAtomIs(APropertyUpCase) and ReadNextUpAtomIs(':=') then
begin
StartPos:=AppPos;
repeat // read till semicolon or end
ReadNextAtom;
if ConstStartPos<1 then
ConstStartPos:=CurPos.StartPos;
EndPos:=CurPos.EndPos;
if CurPos.Flag in [cafEnd,cafSemicolon] then
exit(true);
until CurPos.StartPos>SrcLen;
end;
end
else // Application:=TMyApplication.Create(nil);
if UpAtomIs(':=') and ReadNextUpAtomIs('TMYAPPLICATION')
and ReadNextAtomIsChar('.') and ReadNextUpAtomIs('CREATE') then
repeat // read till semicolon or end
ReadNextAtom;
StartPos:=CurPos.EndPos; // Insert point behind the TMyApplication.Create line.
if CurPos.Flag in [cafEnd,cafSemicolon] then
break;
until CurPos.StartPos>SrcLen;
end; // UpAtomIs('APPLICATION')
until (CurPos.StartPos>SrcLen);
// The statement was not found. Return a good place for insertion.
if StartPos=-1 then
if FirstAppPos <> -1 then
StartPos:=FirstAppPos // Before first Application statement if there is one
else begin
MoveCursorToNodeStart(MainBeginNode);
ReadNextAtom;
StartPos:=CurPos.EndPos; // or after the main Begin.
end;
EndPos:=StartPos; // Both StartPos and EndPos return the same insert point.
end;
function TStandardCodeTool.GatherResourceStringSections(
const CursorPos: TCodeXYPosition; PositionList: TCodeXYPositions): boolean;
function SearchInUsesSection(UsesNode: TCodeTreeNode): boolean;
var
NewCodeTool: TPascalReaderTool;
IntfNode: TCodeTreeNode;
NewCaret: TCodeXYPosition;
Node: TCodeTreeNode;
AnUnitName, InFilename: string;
begin
Result:=false;
if UsesNode=nil then exit(true);
Node:=UsesNode.LastChild;
while Node<>nil do begin
AnUnitName:=ExtractUsedUnitName(Node,@InFilename);
// open the unit
NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,true);
NewCodeTool.BuildTree(lsrImplementationStart);
// search all resource string sections in the interface
IntfNode:=NewCodeTool.FindInterfaceNode;
if (IntfNode<>nil) and (IntfNode.LastChild<>nil) then begin
IntfNode:=IntfNode.LastChild;
while IntfNode<>nil do begin
if IntfNode.Desc=ctnResStrSection then begin
if not NewCodeTool.CleanPosToCaret(IntfNode.StartPos,NewCaret) then
break;
//DebugLn('TStandardCodeTool.GatherResourceStringSections Found Other ',NewCodeTool.MainFilename,' Y=',NewCaret.Y);
PositionList.Add(NewCaret);
end;
IntfNode:=IntfNode.PriorBrother;
end;
end;
Node:=Node.PriorBrother;
end;
Result:=true;
end;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
NewCaret: TCodeXYPosition;
ANode: TCodeTreeNode;
begin
Result:=false;
//DebugLn('TStandardCodeTool.GatherResourceStringSections A ');
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
PositionList.Clear;
ANode:=CursorNode;
while ANode<>nil do begin
case ANode.Desc of
ctnResStrSection:
begin
if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
//DebugLn('TStandardCodeTool.GatherResourceStringSections Found Same Y=',NewCaret.Y);
PositionList.Add(NewCaret);
end;
ctnUsesSection:
if not SearchInUsesSection(ANode) then break;
end;
// go to next node
if ANode.PriorBrother<>nil then begin
ANode:=ANode.PriorBrother;
if (ANode.Desc=ctnInterface) and (ANode.LastChild<>nil) then
ANode:=ANode.LastChild;
end else begin
ANode:=ANode.Parent;
end;
end;
Result:=true;
end;
function TStandardCodeTool.IdentifierExistsInResourceStringSection(
const CursorPos: TCodeXYPosition; const ResStrIdentifier: string): boolean;
var
CleanCursorPos: integer;
ANode: TCodeTreeNode;
begin
Result:=false;
if ResStrIdentifier='' then exit;
// parse source and find clean positions
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
// find resource string section
ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
if (ANode=nil) then exit;
ANode:=ANode.GetNodeOfType(ctnResStrSection);
if ANode=nil then exit;
// search identifier in section
ANode:=ANode.FirstChild;
while ANode<>nil do begin
if (ANode.Desc=ctnConstDefinition)
and CompareSrcIdentifiers(ANode.StartPos,PChar(ResStrIdentifier)) then begin
Result:=true;
exit;
end;
ANode:=ANode.NextBrother;
end;
end;
function TStandardCodeTool.CreateIdentifierFromStringConst(const StartCursorPos,
EndCursorPos: TCodeXYPosition; out Identifier: string;
MaxLen: integer): boolean;
var
StartPos, EndPos: integer;
Dummy: Integer;
IdentStr: String;
ANode: TCodeTreeNode;
CodePosInFront: LongInt;
begin
Result:=false;
if MaxLen<=0 then exit;
// parse source and find clean positions
BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
if (Dummy<>0) and (Dummy<>-1) then exit;
ANode:=FindDeepestNodeAtPos(StartPos,True);
CodePosInFront:=ANode.StartPos;
// read string constants and extract identifier characters
Identifier:='';
MoveCursorToCleanPos(CodePosInFront);
repeat
ReadNextAtom;
//debugln('TStandardCodeTool.CreateIdentifierFromStringConst Atom=',GetAtom);
if (CurPos.StartPos>=EndPos) then break;
if AtomIsStringConstant then begin
IdentStr:=ExtractIdentCharsFromStringConstant(CurPos.StartPos,
StartPos,EndPos,MaxLen-length(Identifier));
//debugln('TStandardCodeTool.CreateIdentifierFromStringConst IdentStr=',IdentStr);
if (IdentStr<>'') then begin
IdentStr[1]:=UpChars[IdentStr[1]];
Identifier:=Identifier+IdentStr;
end;
end;
until length(Identifier)>=MaxLen;
Result:=Identifier<>'';
end;
function TStandardCodeTool.StringConstToFormatString(const StartCursorPos,
EndCursorPos: TCodeXYPosition;
out FormatStringConstant, FormatParameters: string;
out StartInStringConst, EndInStringConst: boolean): boolean;
var
StartPos,EndPos,Dummy: Integer;
begin
Result:=false;
// parse source and find clean positions
BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
if (Dummy<>0) and (Dummy<>-1) then exit;
Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
FormatParameters,StartInStringConst,EndInStringConst);
end;
function TStandardCodeTool.HasInterfaceRegisterProc: boolean;
var
ANode: TCodeTreeNode;
begin
Result:=false;
ANode:=FindDeclarationNodeInInterface('Register',true);
if ANode=nil then exit;
if ANode.Desc=ctnProcedureHead then
ANode:=Anode.Parent;
if (ANode.Desc<>ctnProcedure) then exit;
MoveCursorToNodeStart(ANode);
if not ReadNextUpAtomIs('PROCEDURE') then exit;
if not ReadNextUpAtomIs('REGISTER') then exit;
ReadNextAtom;
if CurPos.Flag<>cafSemicolon then exit;
Result:=true;
end;
function TStandardCodeTool.ConvertDelphiToLazarusSource(AddLRSCode: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
var
Beauty: TBeautifyCodeOptions;
function AddModeDelphiDirective: boolean;
var
ModeDirectivePos: integer;
InsertPos: Integer;
begin
Result:=false;
BuildTree(lsrInterfaceStart);
if not FindModeDirective(false,ModeDirectivePos) then begin
// add {$MODE Delphi} behind source type
if Tree.Root=nil then exit;
MoveCursorToNodeStart(Tree.Root);
ReadNextAtom; // 'unit', 'program', ..
ReadNextAtom; // name
ReadNextAtom; // semicolon
InsertPos:=CurPos.EndPos;
SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
'{$MODE Delphi}');
if not SourceChangeCache.Apply then exit;
end;
// changing mode requires rescan
BuildTree(lsrImplementationStart);
Result:=true;
end;
function ConvertUsedUnits: boolean;
// replace unit 'Windows' with 'LCLIntf' and add 'LResources'
// rename 'in' filenames to case sensitive filename
var
NamePos, InPos: TAtomPosition;
begin
Result:=false;
if FindUnitInAllUsesSections('WINDOWS',NamePos,InPos)
and (InPos.StartPos<1) then begin
if not SourceChangeCache.Replace(gtNone,gtNone,
NamePos.StartPos,NamePos.EndPos,'LCLIntf') then
begin
debugln('ConvertUsedUnits Unable to replace Windows with LCLIntf unit');
exit;
end;
end;
if AddLRSCode then
if not AddUnitToMainUsesSection('LResources','',SourceChangeCache) then
begin
debugln('ConvertUsedUnits Unable to add LResources to main uses section');
exit;
end;
if not RemoveUnitFromAllUsesSections('VARIANTS',SourceChangeCache) then
begin
debugln('ConvertUsedUnits Unable to remove Variants from all uses sections');
exit;
end;
if not FixUsedUnitCase(SourceChangeCache) then
begin
debugln('ConvertUsedUnits Unable to fix unit filename case sensitivity in all uses sections');
exit;
end;
Result:=true;
end;
function RemoveDFMResourceDirective: boolean;
// remove {$R *.dfm} or {$R *.xfm} directive
var
ParamPos: Integer;
ACleanPos: Integer;
StartPos: Integer;
s: String;
begin
Result:=false;
// find $R directive
ACleanPos:=1;
repeat
ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
Scanner.NestedComments,ParamPos);
if (ACleanPos<1) or (ACleanPos>SrcLen) or (ParamPos>SrcLen) then break;
s:=UpperCaseStr(copy(Src,ParamPos,6));
if (Src[ACleanPos]='{')
and ((s='*.DFM}') or (s='*.XFM}'))
then begin
StartPos:=FindLineEndOrCodeInFrontOfPosition(ACleanPos,true);
if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,ParamPos+6,'')
then exit;
break;
end;
ACleanPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments);
until false;
Result:=true;
end;
function AddLRSIncludeDirective: boolean;
// add initialization and {$i unit.lrs} include directive
var
FirstInclude: TCodeBuffer;
LRSFilename: String;
InitializationNode: TCodeTreeNode;
ImplementationNode: TCodeTreeNode;
NewCode: String;
InsertPos: Integer;
LinkIndex: Integer;
begin
Result:=false;
if AddLRSCode then begin
LRSFilename:=ExtractFilenameOnly(MainFilename)+'.lrs';
LinkIndex:=-1;
FirstInclude:=FindNextIncludeInInitialization(LinkIndex);
if (FirstInclude<>nil)
and (CompareFilenames(FirstInclude.Filename,LRSFilename)=0) then begin
// already there
Result:=true;
exit;
end;
if Tree.Root.Desc=ctnUnit then begin
InitializationNode:=FindInitializationNode;
NewCode:=Beauty.GetIndentStr(Beauty.Indent)
+'{$i '+LRSFilename+'}';
if InitializationNode=nil then begin
// add also an initialization section
ImplementationNode:=FindImplementationNode;
InsertPos:=ImplementationNode.EndPos;
NewCode:=Beauty.BeautifyKeyWord(
'initialization')
+Beauty.LineEnd
+NewCode;
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
InsertPos,InsertPos,
NewCode) then exit;
end else begin
InsertPos:=InitializationNode.StartPos+length('initialization');
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
InsertPos,InsertPos,
NewCode) then exit;
end;
end else begin
// only Units supported yet
exit;
end;
end;
Result:=true;
end;
begin
Result:=false;
if SourceChangeCache=nil then exit;
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
DebugLn('ConvertDelphiToLazarusSource AddModeDelphiDirective');
if not AddModeDelphiDirective then exit;
DebugLn('ConvertDelphiToLazarusSource RemoveDFMResourceDirective');
if not RemoveDFMResourceDirective then exit;
DebugLn('ConvertDelphiToLazarusSource AddLRSIncludeDirective');
if not AddLRSIncludeDirective then exit;
DebugLn('ConvertDelphiToLazarusSource ConvertUsedUnits');
if not ConvertUsedUnits then exit;
DebugLn('ConvertDelphiToLazarusSource Apply');
if not SourceChangeCache.Apply then exit;
DebugLn('ConvertDelphiToLazarusSource END');
Result:=true;
end;
function TStandardCodeTool.GetIDEDirectives(DirectiveList: TStrings;
const Filter: TOnIDEDirectiveFilter): boolean;
var
StartPos: Integer;
EndPos: Integer;
begin
Result:=false;
DirectiveList.Clear;
BuildTree(lsrImplementationStart);
EndPos:=1;
repeat
StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
if StartPos<1 then break;
EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
if (Filter=nil) or Filter(Self,StartPos,EndPos) then
DirectiveList.Add(copy(Src,StartPos,EndPos-StartPos));
if EndPos>SrcLen then break;
StartPos:=EndPos;
until false;
Result:=true;
end;
function TStandardCodeTool.SetIDEDirectives(DirectiveList: TStrings;
SourceChangeCache: TSourceChangeCache; const Filter: TOnIDEDirectiveFilter
): boolean;
var
InsertPos: Integer;
EndPos: Integer;
StartPos: Integer;
InsertTxt: String;
ImplementationNode: TCodeTreeNode;
begin
Result:=false;
if SourceChangeCache=nil then exit;
SourceChangeCache.MainScanner:=Scanner;
BuildTree(lsrEnd);
// find first old IDE directive
InsertPos:=FindNextIDEDirective(Src,1,Scanner.NestedComments);
if InsertPos>=1 then begin
EndPos:=FindCommentEnd(Src,InsertPos,Scanner.NestedComments);
if (Filter<>nil) and (not Filter(Self,InsertPos,EndPos)) then
InsertPos:=0;
end else
InsertPos:=0;
// remove all old IDE directives
if InsertPos>=1 then
EndPos:=InsertPos
else
EndPos:=1;
repeat
// find next IDE directive
StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
if StartPos<1 then break;
EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
if (Filter=nil) or Filter(Self,StartPos,EndPos) then begin
// remove also space in front of directive
while (StartPos>1) and (Src[StartPos-1] in [' ',#9]) do dec(StartPos);
// remove also space behind directive
while (EndPos<=SrcLen) and (Src[EndPos] in [' ',#9]) do inc(EndPos);
if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13]) then begin
inc(EndPos);
if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13])
and (Src[EndPos]<>Src[EndPos-1]) then
inc(EndPos);
end;
// remove directive
if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
exit;
end;
if EndPos>SrcLen then break;
StartPos:=EndPos;
until false;
// find a nice insert position
ImplementationNode:=FindImplementationNode;
if (ImplementationNode<>nil)
and (ImplementationNode.StartPos<=InsertPos) then
InsertPos:=0;
if InsertPos<1 then begin
// set default insert position
InsertPos:=1;
if (Tree<>nil) and (Tree.Root<>nil) then
InsertPos:=Tree.Root.StartPos;
end;
// add directives
InsertTxt:=ChompLineEndsAtEnd(DirectiveList.Text);
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
InsertTxt) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
procedure TStandardCodeTool.CalcMemSize(Stats: TCTMemStats);
begin
inherited CalcMemSize(Stats);
end;
function TStandardCodeTool.GatherResourceStringsWithValue(
const CursorPos: TCodeXYPosition; const StringValue: string;
PositionList: TCodeXYPositions): boolean;
procedure CompareStringConst(ANode: TCodeTreeNode);
var
CurValue: String;
NewCaret: TCodeXYPosition;
begin
MoveCursorToNodeStart(ANode);
ReadNextAtom; // read identifier
if not AtomIsIdentifier then exit;
ReadNextAtom; // read =
if CurPos.Flag<>cafEqual then exit;
ReadNextAtom; // read start of string constant
if not AtomIsStringConstant then exit;
// extract string constant value
CurValue:=ReadStringConstantValue(CurPos.StartPos);
if CurValue<>StringValue then exit;
// values are the same
// -> add it to position list
// get x,y position
if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
//DebugLn('TStandardCodeTool.GatherResourceStringsWithValue Found ',MainFilename,' Y=',NewCaret.Y);
PositionList.Add(NewCaret);
end;
var
CleanCursorPos: integer;
ANode: TCodeTreeNode;
begin
Result:=false;
if PositionList=nil then exit;
// parse source and find clean positions
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
// find resource string section
ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
if (ANode=nil) then exit;
ANode:=ANode.GetNodeOfType(ctnResStrSection);
if ANode=nil then exit;
// search identifier in section
ANode:=ANode.FirstChild;
while ANode<>nil do begin
if (ANode.Desc=ctnConstDefinition) then begin
CompareStringConst(ANode);
end;
ANode:=ANode.NextBrother;
end;
end;
function TStandardCodeTool.GetApplicationScaledStatement(BooleanConstStartPos,
EndPos: integer; var AScaled: boolean): boolean;
begin
Result:=false;
AScaled:=false;
if (BooleanConstStartPos<1) or (BooleanConstStartPos>SrcLen) then exit;
MoveCursorToCleanPos(BooleanConstStartPos);
ReadNextAtom;
if (EndPos>0) and (CurPos.EndPos>EndPos) then exit;
if UpAtomIs('TRUE') then
begin
AScaled := True;
Result := True;
end;
if UpAtomIs('FALSE') then
begin
AScaled := False;
Result := True;
end;
end;
function TStandardCodeTool.GatherResourceStringIdents(
const SectionPos: TCodeXYPosition; var IdentTree: TAVLTree): boolean;
var
CleanCursorPos: integer;
ANode: TCodeTreeNode;
begin
Result:=false;
IdentTree:=nil;
// parse source and find clean positions
BuildTreeAndGetCleanPos(SectionPos,CleanCursorPos);
// find resource string section
ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
if (ANode=nil) then exit;
ANode:=ANode.GetNodeOfType(ctnResStrSection);
if ANode=nil then exit;
// search identifier in section
ANode:=ANode.FirstChild;
while ANode<>nil do begin
if (ANode.Desc=ctnConstDefinition) then begin
if IdentTree=nil then
IdentTree:=TAVLTree.Create(TListSortCompare(@BasicCodeTools.CompareIdentifiers));
IdentTree.Add(@Src[ANode.StartPos]);
end;
ANode:=ANode.NextBrother;
end;
Result:=true;
end;
function TStandardCodeTool.FindNearestResourceString(const CursorPos,
SectionPos: TCodeXYPosition; var NearestPos: TCodeXYPosition): boolean;
var
CursorTool, SectionTool: TStandardCodeTool;
IdentTree: TAVLTree;
NearestNode: TAVLTreeNode;
NearestCleanPos: Integer;
begin
Result:=false;
NearestPos.Code:=nil;
// get both codetools
if not Assigned(OnGetCodeToolForBuffer) then exit;
CursorTool:=
TStandardCodeTool(OnGetCodeToolForBuffer(Self,CursorPos.Code,true));
SectionTool:=
TStandardCodeTool(OnGetCodeToolForBuffer(Self,SectionPos.Code,true));
if (CursorTool=nil) or (SectionTool=nil) then exit;
// get all resourcestring identifiers
IdentTree:=nil;
Result:=SectionTool.GatherResourceStringIdents(SectionPos,IdentTree);
if IdentTree=nil then exit;
try
// find nearest resourcestring identifier in the cursor source
NearestNode:=CursorTool.FindNearestIdentifierNode(CursorPos,IdentTree);
if NearestNode=nil then exit;
// convert node to cleanpos
NearestCleanPos:={%H-}PtrUInt(NearestNode.Data)-{%H-}PtrUInt(@SectionTool.Src[1])+1;
// convert cleanpos to caret
CleanPosToCaret(NearestCleanPos,NearestPos);
finally
IdentTree.Free;
end;
Result:=true;
end;
function TStandardCodeTool.AddResourceString(const SectionPos: TCodeXYPosition;
const NewIdentifier, NewValue: string;
InsertPolicy: TResourcestringInsertPolicy;
const NearestPos: TCodeXYPosition;
SourceChangeCache: TSourceChangeCache): boolean;
var
CleanSectionPos: integer;
ANode, SectionNode: TCodeTreeNode;
Indent: Integer;
InsertPos: Integer;
InsertSrc: String;
NearestCleanPos: integer;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
//DebugLn('TStandardCodeTool.AddResourcestring A ',NewIdentifier,'=',NewValue,' ');
if (NewIdentifier='') or (length(NewIdentifier)>255) then exit;
if SourceChangeCache=nil then exit;
SourceChangeCache.MainScanner:=Scanner;
// parse source and find clean positions
//DebugLn('TStandardCodeTool.AddResourcestring B');
BuildTreeAndGetCleanPos(SectionPos,CleanSectionPos);
//DebugLn('TStandardCodeTool.AddResourcestring C');
// find resource string section
SectionNode:=FindDeepestNodeAtPos(CleanSectionPos,true);
if (SectionNode=nil) then exit;
SectionNode:=SectionNode.GetNodeOfType(ctnResStrSection);
if SectionNode=nil then exit;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
//DebugLn('TStandardCodeTool.AddResourcestring D SectionChilds=',SectionNode.FirstChild<>nil);
// find insert position
if SectionNode.FirstChild=nil then begin
// no resourcestring in this section yet -> append as first child
Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
InsertPos:=SectionNode.StartPos+length('RESOURCESTRING');
end else begin
// search insert position
case InsertPolicy of
rsipAlphabetically:
begin
// insert new identifier alphabetically
ANode:=SectionNode.FirstChild;
while (ANode<>nil) do begin
if (ANode.Desc=ctnConstDefinition)
and (CompareIdentifiers(@Src[ANode.StartPos],
PChar(Pointer(NewIdentifier)))<0)
then
break;
ANode:=ANode.NextBrother;
end;
if ANode=nil then begin
// append new identifier as last
Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
end else begin
// insert in front of node
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
end;
end;
rsipContext:
begin
// find nearest
ANode:=nil;
if (NearestPos.Code<>nil)
and (CaretToCleanPos(NearestPos,NearestCleanPos)=0) then begin
ANode:=SectionNode.FirstChild;
while (ANode<>nil) do begin
if (ANode.Desc=ctnConstDefinition)
and (ANode.StartPos<=NearestCleanPos)
and (ANode.EndPos>NearestCleanPos)
then begin
break;
end;
ANode:=ANode.NextBrother;
end;
end;
if ANode=nil then begin
// append new identifier as last
Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
end else begin
// insert behind node
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(ANode.EndPos);
end;
end;
else
begin
// append new identifier
Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
end;
end;
end;
//DebugLn('TStandardCodeTool.AddResourcestring E Indent=',Indent,' InsertPos=',InsertPos,' ',copy(Src,InsertPos-9,8),'|',copy(Src,InsertPos,8));
// insert
InsertSrc:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
NewIdentifier+' = '+NewValue+';',Indent);
//DebugLn('TStandardCodeTool.AddResourcestring F "',InsertSrc,'"');
SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,InsertSrc);
SourceChangeCache.Apply;
Result:=true;
//DebugLn('TStandardCodeTool.AddResourcestring END ',Result);
end;
function TStandardCodeTool.FindPublishedVariable(const AClassName,
AVarName: string; ExceptionOnClassNotFound: boolean): TCodeTreeNode;
var ClassNode, SectionNode: TCodeTreeNode;
begin
Result:=nil;
if (AClassName='') or (length(AClassName)>255) then
RaiseExceptionFmt(20170421201129,ctsinvalidClassName, [AClassName]);
if AVarName='' then exit;
BuildTree(lsrImplementationStart);
ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
if ClassNode=nil then begin
if ExceptionOnClassNotFound then
RaiseExceptionFmt(20170421201136,ctsclassNotFound, [AClassName])
else
exit;
end;
SectionNode:=ClassNode.FirstChild;
while (SectionNode<>nil) do begin
if SectionNode.Desc=ctnClassPublished then begin
Result:=SectionNode.FirstChild;
while Result<>nil do begin
if (Result.Desc=ctnVarDefinition) then begin
MoveCursorToNodeStart(Result);
if ReadNextAtomIsIdentifier(PChar(AVarName)) then
exit;
end;
Result:=Result.NextBrother;
end;
end;
SectionNode:=SectionNode.NextBrother;
end;
end;
function TStandardCodeTool.GatherPublishedVarTypes(const AClassName: string;
out VarNameToType: TStringToStringTree): boolean;
var
ClassNode, SectionNode, Node, VarNode, TypeNode: TCodeTreeNode;
SimpleTypes: TStringToStringTree;
VarName, NewType, VarType, CurUnitName: String;
Params: TFindDeclarationParams;
aContext: TFindContext;
begin
Result:=true;
VarNameToType:=nil;
// search class
if (AClassName='') or (length(AClassName)>255) then
RaiseExceptionFmt(20230411091809,ctsinvalidClassName, [AClassName]);
BuildTree(lsrImplementationStart);
ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
if ClassNode=nil then
RaiseExceptionFmt(20230411091811,ctsclassNotFound, [AClassName]);
// traverse class declaration
SimpleTypes:=TStringToStringTree.Create(false);
try
SectionNode:=ClassNode.FirstChild;
while (SectionNode<>nil) do begin
if SectionNode.Desc=ctnClassPublished then begin
Node:=SectionNode.FirstChild;
while Node<>nil do begin
VarNode:=Node;
Node:=Node.NextBrother;
if (VarNode.Desc<>ctnVarDefinition) then continue;
// published variable
TypeNode:=FindTypeNodeOfDefinition(VarNode);
if TypeNode=nil then continue;
if TypeNode.Desc<>ctnIdentifier then continue;
// read variable name
VarName:=GetIdentifier(@Src[VarNode.StartPos]);
//debugln(['TStandardCodeTool.GatherPublishedVarTypes VarName="',VarName,'"']);
// read variable type
MoveCursorToNodeStart(TypeNode);
ReadNextAtom;
if not AtomIsIdentifier then
continue;
VarType:=GetAtom;
while ReadNextAtomIs('.') do begin
ReadNextAtom;
if not AtomIsIdentifier then
break;
VarType:=VarType+'.'+GetAtom;
end;
//debugln(['TStandardCodeTool.GatherPublishedVarTypes VarType="',VarType,'"']);
if (CurPos.Flag<>cafSemicolon) then begin
//debugln(['TStandardCodeTool.GatherPublishedVarTypes WARNING not a simple type: ',VarName]);
continue; // e.g. specialize A<B>
end;
if (Pos('.',VarType)<1) then begin
// simple type without unitname
NewType:=SimpleTypes[VarType];
if NewType='' then
begin
// resolve simple type
Params:=TFindDeclarationParams.Create;
try
Params.ContextNode:=TypeNode;
// resolve alias
aContext:=FindBaseTypeOfNode(Params,TypeNode);
//debugln(['TStandardCodeTool.GatherPublishedVarTypes Type "',VarType,'" found at ',FindContextToString(aContext,false)]);
if aContext.Node.Desc=ctnClass then
VarType:=aContext.Tool.ExtractClassName(aContext.Node,false);
CurUnitName:=aContext.Tool.GetSourceName(false);
// unitname/vartype
NewType:=CurUnitName+'/'+VarType;
//debugln(['TStandardCodeTool.GatherPublishedVarTypes Resolved: "',VarType,'" = "',NewType,'"']);
SimpleTypes[VarType]:=NewType;
finally
Params.Free;
end;
end;
VarType:=NewType;
end;
//debugln(['TStandardCodeTool.GatherPublishedVarTypes Added ',VarName,':',VarType]);
if VarNameToType=nil then
VarNameToType:=TStringToStringTree.Create(false);
VarNameToType[VarName]:=VarType;
end;
end;
SectionNode:=SectionNode.NextBrother;
end;
finally
SimpleTypes.Free;
end;
end;
function TStandardCodeTool.AddPublishedVariable(const AClassName,
VarName, VarType: string; SourceChangeCache: TSourceChangeCache): boolean;
var ClassNode, SectionNode: TCodeTreeNode;
Indent, InsertPos: integer;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
if (AClassName='') or (length(AClassName)>255) then
RaiseExceptionFmt(20170421201143,ctsinvalidClassName2, [AClassName]);
if (VarName='') or (length(VarName)>255) then
RaiseExceptionFmt(20170421201152,ctsinvalidVariableName, [VarName]);
if (VarType='') or (length(VarType)>255) then
RaiseExceptionFmt(20170421201158,ctsinvalidVariableType, [VarType]);
if (SourceChangeCache=nil) then
RaiseException(20170421201203,'missing SourceChangeCache');
if FindPublishedVariable(AClassName,VarName,true)<>nil then
begin
Result:=true;
exit;
end;
ClassNode:=FindClassNodeInInterface(AClassName,true,false,true);
if ClassNode=nil then
RaiseExceptionFmt(20170421201208,ctsclassNotFound, [AClassName]);
SectionNode:=ClassNode.FirstChild;
if (SectionNode.NextBrother<>nil)
and (SectionNode.NextBrother.Desc=ctnClassPublished) then
SectionNode:=SectionNode.NextBrother;
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
if SectionNode.FirstChild<>nil then begin
Indent:=Beauty.GetLineIndent(Src,SectionNode.FirstChild.StartPos);
end else begin
Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
end;
InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos);
SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
Beauty.BeautifyStatement(VarName+':'+VarType+';',Indent)
);
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.RemovePublishedVariable(const AClassName,
AVarName: string; ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
var VarNode: TCodeTreeNode;
FromPos, ToPos: integer;
begin
Result:=false;
VarNode:=FindPublishedVariable(AClassName,AVarName,
ExceptionOnClassNotFound);
if VarNode=nil then exit;
if (VarNode.PriorBrother<>nil)
and (VarNode.PriorBrother.Desc=ctnVarDefinition)
and (VarNode.PriorBrother.FirstChild=nil) then begin
// variable definition has the form 'PriorVarName, VarName: VarType;'
// or 'PriorVarName, VarName, NextVarName: VarType'
// -> delete only ', VarName'
MoveCursorToNodeStart(VarNode.PriorBrother);
ReadNextAtom; // read 'PriorVarName'
ReadNextAtom; // read ','
FromPos:=CurPos.StartPos;
ReadNextAtom; // read 'VarName'
ReadNextAtom; // read ':'
ToPos:=CurPos.StartPos;
end else begin
if VarNode.FirstChild<>nil then begin
// variable definition has the form 'VarName: VarType;'
// -> delete whole line
FromPos:=FindLineEndOrCodeInFrontOfPosition(VarNode.StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(VarNode.EndPos);
//debugln(['TStandardCodeTool.RemovePublishedVariable ',dbgstr(copy(Src,FromPos,ToPos-FromPos))]);
end else begin
// variable definition has the form 'VarName, NextVarName: VarType;'
// -> delete only 'VarName, '
FromPos:=VarNode.StartPos;
ToPos:=VarNode.NextBrother.StartPos;
end;
end;
SourceChangeCache.MainScanner:=Scanner;
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.RenamePublishedVariable(const AClassName,
AOldVarName: string; const NewVarName, VarType: shortstring;
ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
var
TypeNode, VarNode: TCodeTreeNode;
ApplyNeeded: Boolean;
begin
Result:=false;
if (NewVarName='') or (VarType='') then exit;
BuildTree(lsrEnd);
VarNode:=FindPublishedVariable(AClassName,AOldVarName,
ExceptionOnClassNotFound);
if VarNode<>nil then begin
// old variable found
// check type
TypeNode:=FindTypeNodeOfDefinition(VarNode);
MoveCursorToNodeStart(TypeNode);
ReadNextAtom;
SourceChangeCache.MainScanner:=Scanner;
ApplyNeeded:=false;
if (not AtomIsIdentifier(@VarType[1])) then begin
// change the type
ApplyNeeded:=true;
if not SourceChangeCache.Replace(gtNone,gtNone,
CurPos.StartPos,CurPos.EndPos,VarType)
then begin
RaiseException(20170421201215,'Unable to replace type');
end;
end;
// rename variable in source
if not ReplaceWord(AOldVarName,NewVarName,false,SourceChangeCache,true)
then
exit;
Result:=(not ApplyNeeded) or SourceChangeCache.Apply;
end else begin
// old variable not found -> add it
Result:=AddPublishedVariable(AClassName,NewVarName,VarType,
SourceChangeCache);
end;
end;
function TStandardCodeTool.GatherPublishedClassElements(
const TheClassName: string;
ExceptionOnClassNotFound, WithVariables, WithMethods, WithProperties,
WithAncestors: boolean;
out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
function Add(AFindContext: PFindContext): boolean;
var
ClassNode: TCodeTreeNode;
CurTool: TFindDeclarationTool;
SectionNode: TCodeTreeNode;
ANode: TCodeTreeNode;
CurProcName: String;
NewNodeExt: TCodeTreeNodeExtension;
CurPropName: String;
CurVarName: String;
begin
Result:=false;
ClassNode:=AFindContext^.Node;
if (ClassNode=nil)
or (not (ClassNode.Desc in AllClasses)) then exit;
CurTool:=AFindContext^.Tool;
SectionNode:=ClassNode.FirstChild;
while (SectionNode<>nil) do begin
if SectionNode.Desc=ctnClassPublished then begin
ANode:=SectionNode.FirstChild;
while ANode<>nil do begin
if (ANode.Desc=ctnProcedure) and WithMethods then begin
CurProcName:=CurTool.ExtractProcName(ANode,[]);
{$IFDEF VerboseDanglingComponentEvents}
debugln('TStandardCodeTool.GatherPublishedClassElements CurProcName="',CurProcName,'"');
{$ENDIF}
NewNodeExt:=TCodeTreeNodeExtension.Create;
with NewNodeExt do begin
Node:=ANode;
Txt:=CurProcName;
end;
TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
end
else if (ANode.Desc=ctnVarDefinition) and WithVariables then begin
CurVarName:=CurTool.ExtractDefinitionName(ANode);
NewNodeExt:=TCodeTreeNodeExtension.Create;
with NewNodeExt do begin
Node:=ANode;
Txt:=CurVarName;
end;
TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
end
else if (ANode.Desc=ctnProperty) and WithProperties then begin
CurPropName:=CurTool.ExtractPropName(ANode,false);
NewNodeExt:=TCodeTreeNodeExtension.Create;
with NewNodeExt do begin
Node:=ANode;
Txt:=CurPropName;
end;
TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
end;
ANode:=ANode.NextBrother;
end;
end;
SectionNode:=SectionNode.NextBrother;
end;
Result:=true;
end;
var
ClassNode: TCodeTreeNode;
AncestorList: TFPList;// of PFindContext
i: Integer;
begin
Result:=false;
TreeOfCodeTreeNodeExtension:=nil;
if (TheClassName='') or (length(TheClassName)>255) then
RaiseExceptionFmt(20170421201221,ctsInvalidClassName, [TheClassName]);
{$IFDEF VerboseDanglingComponentEvents}
DebugLn(['TStandardCodeTool.GatherPublishedClassElements BEFORE buildtree']);
{$ENDIF}
BuildTree(lsrImplementationStart);
{$IFDEF VerboseDanglingComponentEvents}
DebugLn(['TStandardCodeTool.GatherPublishedClassElements after buildtree']);
{$ENDIF}
ClassNode:=FindClassNodeInInterface(TheClassName,true,false,
ExceptionOnClassNotFound);
if ClassNode=nil then exit;
AncestorList:=nil;
try
if WithAncestors then begin
if not FindClassAndAncestors(ClassNode,AncestorList,true) then exit;
end else begin
AddFindContext(AncestorList,CreateFindContext(Self,ClassNode));
end;
TreeOfCodeTreeNodeExtension:=TAVLTree.Create(@CompareCodeTreeNodeExt);
for i:=0 to AncestorList.Count-1 do begin
if not Add(PFindContext(AncestorList[i])) then exit;
end;
//debugln(['TStandardCodeTool.GatherPublishedClassElements END']);
finally
FreeListOfPFindContext(AncestorList);
end;
Result:=true;
end;
function TStandardCodeTool.RetypeClassVariables(const AClassName: string;
ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
SourceChangeCache: TSourceChangeCache; SearchImplementationToo: boolean): boolean;
var
ClassNode: TCodeTreeNode;
Node: TCodeTreeNode;
TypeNode: TCodeTreeNode;
OldType: String;
NewType: string;
HasChanged: Boolean;
begin
Result:=false;
if SearchImplementationToo then begin
BuildTree(lsrEnd);
ClassNode:=FindClassNodeInUnit(AClassName,true,false,false,
ExceptionOnClassNotFound)
end
else begin
BuildTree(lsrImplementationStart);
ClassNode:=FindClassNodeInInterface(AClassName,true,false,
ExceptionOnClassNotFound);
end;
if ClassNode=nil then exit;
if (ListOfTypes=nil) or (ListOfTypes.Tree.Count=0) then exit(true);
HasChanged:=false;
Node:=ClassNode.FirstChild;
while (Node<>nil) and (Node.HasAsParent(ClassNode)) do begin
if (Node.Desc=ctnVarDefinition) and (Node.FirstChild<>nil) then begin
TypeNode:=Node.FirstChild;
if TypeNode.Desc=ctnIdentifier then begin
MoveCursorToNodeStart(TypeNode);
ReadNextAtom;
ReadNextAtom;
if CurPos.Flag=cafPoint then begin
// skip unitname
ReadNextAtom;
end else begin
UndoReadNextAtom;
end;
// cursor is now on identifier
OldType:=GetAtom;
if ListOfTypes.Contains(OldType) then begin
NewType:=ListOfTypes[OldType];
if OldType<>NewType then begin
// change type (or case)
if not HasChanged then begin
HasChanged:=true;
SourceChangeCache.MainScanner:=Scanner;
end;
if not SourceChangeCache.Replace(gtNone,gtNone,
CurPos.StartPos,CurPos.EndPos,NewType)
then
exit(false);
end;
end;
end;
Node:=Node.NextSkipChilds;
end else
Node:=Node.Next;
end;
if HasChanged then begin
if not SourceChangeCache.Apply then exit;
end;
Result:=true;
end;
function TStandardCodeTool.FindDanglingComponentEvents(
const TheClassName: string; RootComponent: TComponent;
ExceptionOnClassNotFound, SearchInAncestors: boolean; out
ListOfPInstancePropInfo: TFPList;
const OverrideGetMethodName: TOnGetMethodname): boolean;
var
PublishedMethods: TAVLTree;
procedure AddDanglingEvent(Instance: TPersistent; PropInfo: PPropInfo);
var
NewItem: PInstancePropInfo;
begin
New(NewItem);
NewItem^.Instance:=Instance;
NewItem^.PropInfo:=PropInfo;
if ListOfPInstancePropInfo=nil then ListOfPInstancePropInfo:=TFPList.Create;
ListOfPInstancePropInfo.Add(NewItem);
{$IFDEF VerboseDanglingComponentEvents}
debugln('AddDanglingEvent ',DbgSName(Instance),' ',PropInfo^.Name);
{$ENDIF}
end;
procedure CheckMethodsInPersistent(APersistent: TPersistent);
var
TypeInfo: PTypeInfo;
TypeData: PTypeData;
PropInfo: PPropInfo;
PropList: PPropList;
CurCount,i: integer;
PropType: PTypeInfo;
NodeExt: TCodeTreeNodeExtension;
CurMethod: TMethod;
CurMethodName: String;
ObjValue: TObject;
begin
if APersistent=nil then exit;
{$IFDEF VerboseDanglingComponentEvents}
debugln('TStandardCodeTool.FindDanglingComponentEvents.CheckMethodsInPersistent Checking ',DbgSName(APersistent));
{$ENDIF}
// read all properties and remove doubles
TypeInfo:=APersistent.ClassInfo;
repeat
// read all property infos of current class
TypeData:=GetTypeData(TypeInfo);
// read property count
CurCount:=GetPropList(TypeInfo,PropList);
try
{$IFDEF VerboseDanglingComponentEvents}
debugln(' UnitName=',TypeData^.UnitName,' Type=',TypeInfo^.Name,' CurPropCount=',dbgs(CurCount));
{$ENDIF}
// read properties
for i:=0 to CurCount-1 do begin
PropInfo:=PropList^[i];
{$IFDEF VerboseDanglingComponentEvents}
debugln(' Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name);
{$ENDIF}
PropType:=PropInfo^.PropType;
if (PropType^.Kind=tkMethod) then begin
// RTTI property is method
// -> search method in source
CurMethod:=GetMethodProp(APersistent,PropInfo);
if (CurMethod.Data<>nil) or (CurMethod.Code<>nil) then begin
if Assigned(OverrideGetMethodName) then
CurMethodName:=OverrideGetMethodName(CurMethod,RootComponent)
else
CurMethodName:=OnGetMethodName(CurMethod,RootComponent);
{$IFDEF VerboseDanglingComponentEvents}
debugln(' Persistent ',DbgSName(APersistent),' Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name,' CurMethodName="',CurMethodName,'"');
{$ENDIF}
if CurMethodName<>'' then begin
NodeExt:=FindCodeTreeNodeExt(PublishedMethods,CurMethodName);
if NodeExt=nil then begin
// method not found -> dangling event
AddDanglingEvent(APersistent,PropInfo);
end;
end;
end;
end else if (PropType^.Kind=tkClass) then begin
// RTTI property is class instance
ObjValue := TObject(GetObjectProp(APersistent, PropInfo));
if ObjValue is TCollection then begin
// collection
end else if (ObjValue is TPersistent)
and (not (ObjValue is TComponent)
or (csSubComponent in TComponent(ObjValue).ComponentStyle))
then begin
// sub persistent (e.g. Canvas.Font)
//debugln(['CheckMethodsInPersistent sub persistent: ',DbgSName(ObjValue)]);
CheckMethodsInPersistent(TPersistent(ObjValue));
end;
end;
end;
finally
FreeMem(PropList);
end;
TypeInfo:=TypeData^.ParentInfo;
until TypeInfo=nil;
end;
var
i: Integer;
Collector: TComponentChildCollector;
AllComponents: TFPList;
begin
PublishedMethods:=nil;
ListOfPInstancePropInfo:=nil;
Collector:=nil;
AllComponents:=nil;
try
// search all available published methods
{$IFDEF VerboseDanglingComponentEvents}
debugln('TStandardCodeTool.FindDanglingComponentEvents A ',MainFilename,' ',DbgSName(RootComponent));
{$ENDIF}
Result:=GatherPublishedClassElements(TheClassName,ExceptionOnClassNotFound,
false,true,false,SearchInAncestors,
PublishedMethods);
if not Result then exit;
// go through all components
Collector:=TComponentChildCollector.Create;
AllComponents:=Collector.GetComponents(RootComponent,true);
for i:=0 to AllComponents.Count-1 do
CheckMethodsInPersistent(TComponent(AllComponents[i]));
finally
Collector.Free;
DisposeAVLTree(PublishedMethods);
end;
end;
function TStandardCodeTool.RemoveIdentifierDefinition(
const CursorPos: TCodeXYPosition; SourceChangeCache: TSourceChangeCache
): boolean;
var
CleanCursorPos: integer;
Node: TCodeTreeNode;
PrevSibling: TCodeTreeNode;
NextSibling: TCodeTreeNode;
DeleteStartPos: LongInt;
DeleteEndPos: LongInt;
DeleteFirstTokenOfLine: Boolean;
begin
Result:=false;
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
Node:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
if Node.Desc in AllIdentifierDefinitions then begin
// 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;
// type X = integer;
// const X = 0;
// const X : integer = 0;
PrevSibling:=nil;
NextSibling:=nil;
if (Node.PriorBrother<>nil) and (Node.PriorBrother.FirstChild=nil) then
PrevSibling:=Node.PriorBrother;
if (Node.FirstChild=nil) and (Node.NextBrother<>nil) then
NextSibling:=Node.NextBrother;
DeleteStartPos:=Node.StartPos;
DeleteEndPos:=Node.StartPos+GetIdentLen(@Src[Node.StartPos]);
if NextSibling<>nil then begin
// var i, X, j: integer; -> var i, [X, ]j: integer;
// var X, i: integer; -> var [X, ]i: integer;
MoveCursorToCleanPos(Node.StartPos);
ReadNextAtom;
AtomIsIdentifierE;
if not ReadNextAtomIsChar(',') then
RaiseCharExpectedButAtomFound(20170421201227,',');
DeleteEndPos:=CurPos.EndPos;
end else if PrevSibling<>nil then begin
// var i, X: integer; -> var i[, X]: integer;
MoveCursorToCleanPos(PrevSibling.StartPos);
ReadNextAtom;
AtomIsIdentifierE;
if not ReadNextAtomIsChar(',') then
RaiseCharExpectedButAtomFound(20170421201233,',');
DeleteStartPos:=CurPos.StartPos;
end else begin
// delete whole declaration
if (Node.Parent.Desc in AllDefinitionSections)
and (Node.PriorBrother=nil) and (Node.NextBrother=nil) then begin
// delete whole section
DeleteStartPos:=Node.Parent.StartPos;
DeleteEndPos:=Node.Parent.EndPos;
end else if Node.Parent.Desc=ctnParameterList then begin
// delete whole parameter including modifier, type and default value
if Node.PriorBrother<>nil then begin
// ... var i: integer; var X: ... -> ... var i: integer[; var X: ...
MoveCursorToCleanPos(Node.PriorBrother.EndPos);
repeat
ReadNextAtom;
if CurPos.Flag=cafSemicolon then begin
DeleteStartPos:=CurPos.EndPos;
break;
end;
until CurPos.StartPos>=Node.StartPos;
end else begin
// (var X: ... -> ([; X: ...
MoveCursorToCleanPos(Node.Parent.StartPos);
ReadNextAtom;
if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
DeleteStartPos:=CurPos.EndPos;
end;
if Node.NextBrother<>nil then begin
// ... var X: integer; var i: ... -> .. var X: integer;] var i: ...
DeleteEndPos:=Node.PriorBrother.EndPos;
end else begin
// ... var X: integer) -> .. var X: integer])
DeleteEndPos:=Node.EndPos;
end;
end else begin
// keep section, delete whole declaration
DeleteEndPos:=Node.EndPos;
end;
end;
// include corresponding comments
DeleteFirstTokenOfLine:=FindFirstNonSpaceCharInLine(Src,DeleteStartPos)=DeleteStartPos;
//DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition ',dbgstr(copy(Src,FindFirstNonSpaceCharInLine(Src,DeleteStartPos),10))]);
DeleteEndPos:=FindLineEndOrCodeAfterPosition(DeleteEndPos,true,DeleteFirstTokenOfLine);
if DeleteFirstTokenOfLine and (Src[DeleteEndPos-1] in [#10,#13]) then begin
// delete first and last token of line
// => remove the entire line
DeleteStartPos:=GetLineStartPosition(Src,DeleteStartPos);
end;
//DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition "',dbgstr(copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos)),'" IncludeLineEnd=',DeleteFirstTokenOfLine]);
// delete
SourceChangeCache.MainScanner:=Scanner;
if not SourceChangeCache.Replace(gtNone,gtNone,DeleteStartPos,DeleteEndPos,'')
then exit;
Result:=SourceChangeCache.Apply;
end;
end;
function TStandardCodeTool.InsertStatements(
InsertPos: TInsertStatementPosDescription; Statements: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
CleanCursorPos: integer;
begin
BeginParsingAndGetCleanPos(lsrEnd,InsertPos.CodeXYPos,CleanCursorPos);
Result:=InsertStatements(CleanCursorPos,Statements,InsertPos.Indent,
InsertPos.FrontGap,InsertPos.AfterGap,SourceChangeCache);
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.InsertStatements(CleanPos: integer;
Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
SourceChangeCache: TSourceChangeCache): boolean;
{
ToDo: check for "uses" in Statements and extend uses section
e.g. "uses unit1, unit2 in 'filename'; statements
ToDo: check for single statement (e.g. for .. do | dosome;) and add begin/end
}
var
Node: TCodeTreeNode;
SameArea: TAtomPosition;
BeautifyFlags: TBeautifyCodeFlags;
begin
Node:=FindDeepestNodeAtPos(CleanPos,true);
if not (Node.Desc in AllPascalStatements) then begin
MoveCursorToCleanPos(CleanPos);
RaiseException(20170421201247,ctsInvalidPositionForInsertionOfStatements);
end;
if Node.Desc=ctnBeginBlock then
Node:=BuildSubTreeAndFindDeepestNodeAtPos(Node,CleanPos,true);
GetCleanPosInfo(Node.StartPos,CleanPos,false,SameArea);
if (SameArea.StartPos>SrcLen) or (not IsSpaceChar[Src[SameArea.StartPos]])
then begin
MoveCursorToCleanPos(CleanPos);
RaiseException(20170421201255,ctsInvalidPositionForInsertionOfStatements);
end;
SourceChangeCache.MainScanner:=Scanner;
BeautifyFlags:=[bcfIndentExistingLineBreaks];
if FrontGap in [gtNone,gtSpace] then
include(BeautifyFlags,bcfDoNotIndentFirstLine);
Statements:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
Statements,Indent,BeautifyFlags);
Result:=SourceChangeCache.Replace(FrontGap,AfterGap,CleanPos,CleanPos,Statements);
end;
function TStandardCodeTool.FindBlockCounterPart(
const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
NewTopLine: integer; IncludeCloseToken: Boolean): boolean;
// jump from bracket-open to bracket-close or 'begin' to 'end'
// or 'until' to 'repeat' ...
var CleanCursorPos: integer;
begin
Result:=false;
BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
// read word at cursor
MoveCursorToCleanPos(CleanCursorPos);
if Src[CurPos.StartPos] in ['(','[','{'] then begin
// jump forward to matching bracket
ReadNextAtom;
if not ReadForwardTilAnyBracketClose then exit;
end else if Src[CurPos.StartPos] in [')',']','}'] then begin
// jump backward to matching bracket
ReadNextAtom;
if not ReadBackwardTilAnyBracketClose then exit;
end else begin
if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
dec(CurPos.StartPos);
MoveCursorToCleanPos(CurPos.StartPos);
ReadNextAtom;
if CurPos.EndPos=CurPos.StartPos then exit;
// read till block keyword counterpart
if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
// read forward till END, FINALLY, EXCEPT
ReadTilBlockEnd(true,false);
end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('UNTIL') then
begin
// read backward till BEGIN, CASE, ASM, RECORD, REPEAT
ReadBackTilBlockEnd(true);
end else
exit;
end;
if IncludeCloseToken then
ReadNextAtom;
// CursorPos now contains the counter block keyword
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
end;
function TStandardCodeTool.FindBlockStart(const CursorPos: TCodeXYPosition; out
NewPos: TCodeXYPosition; out NewTopLine: integer; SkipStart: boolean
): boolean;
// jump to beginning of current block
// e.g. bracket open, 'begin', 'repeat', ...
var CleanCursorPos: integer;
CursorOnStart: Boolean;
Node: TCodeTreeNode;
MinPos: Integer;
begin
Result:=false;
// scan code
BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
// read word at cursor
MoveCursorToCleanPos(CleanCursorPos);
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
dec(CurPos.StartPos);
MoveCursorToCleanPos(CurPos.StartPos);
ReadNextAtom;
try
if CurPos.StartPos>=SrcLen then begin
ReadPriorAtom;
if CurPos.StartPos<1 then begin
MoveCursorToCleanPos(1);
exit(true);
end;
end;
Node:=FindDeepestNodeAtPos(CleanCursorPos,false);
//if Node<>nil then debugln(['TStandardCodeTool.FindBlockStart ',Node.DescAsString]);
if (Node=nil)
or (Node.Desc in (AllPascalStatements+AllPascalTypes-AllClasses))
or (Src[CurPos.StartPos] in [')',']','}'])
then begin
MinPos:=1;
if Node<>nil then MinPos:=Node.StartPos;
repeat
//debugln(['TStandardCodeTool.FindBlockStart atom ',CleanPosToStr(CurPos.StartPos),' ',GetAtom]);
if (CurPos.StartPos<0) then begin
// start of source found -> this is always a block start
MoveCursorToCleanPos(1);
exit(true);
end
else if Src[CurPos.StartPos] in [')',']','}'] then begin
// jump backward to matching bracket
CursorOnStart:=(CleanCursorPos=CurPos.StartPos);
if not ReadBackwardTilAnyBracketClose then exit;
if CursorOnStart then exit(true);
end
else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
begin
// block start found
if (CurPos.StartPos<CleanCursorPos) or (not SkipStart) then
exit(true);
end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('UNTIL') then
begin
// read backward till BEGIN, CASE, ASM, RECORD, REPEAT
CursorOnStart:=(CleanCursorPos>=CurPos.StartPos)
and (CleanCursorPos<CurPos.EndPos);
ReadBackTilBlockEnd(true);
if CursorOnStart then exit(true);
end;
ReadPriorAtom;
until CurPos.StartPos<MinPos;
end;
if Node<>nil then begin
if SkipStart and (CleanCursorPos=Node.StartPos) then begin
while (Node<>nil) and (Node.StartPos=CleanCursorPos) do
Node:=Node.Parent;
if Node<>nil then
MoveCursorToCleanPos(Node.StartPos)
else
MoveCursorToCleanPos(1);
exit(true);
end;
if CleanCursorPos>=Node.StartPos then begin
MoveCursorToCleanPos(Node.StartPos);
exit(true);
end;
end;
finally
if Result then begin
// CursorPos now contains the block start atom
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
end;
end;
end;
function TStandardCodeTool.GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
{ search a block (e.g. begin..end) that looks unclosed, i.e. 'begin'
without 'end' or 'begin' with 'end' in a different column.
This function can be used as GuessNextUnclosedBlock, because it ignores blocks
in front of CursorPos.
Examples for good blocks:
repeat
until
begin end // start and end of block in the same line
if expr then begin // first char in line is relevant, not the block keyword
end
class;
Examples for bad blocks:
begin // block start and end has different indenting
end
asm // 'end.' is source end, never asm end
end.
try // different indenting
finally
repeat // keywords do not match
end
}
var CleanCursorPos: integer;
begin
Result:=false;
BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
// start reading at beginning of code
MoveCursorToCleanPos(1);
BuildBlockKeyWordFuncList;
if ReadTilGuessedUnclosedBlock(CleanCursorPos,false) then
Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
//WriteDebugTreeReport;
end;
function TStandardCodeTool.FindBlockCleanBounds(
const CursorPos: TCodeXYPosition; out BlockCleanStart, BlockCleanEnd: integer
): boolean;
var
CleanCursorPos: integer;
BlockStartFound: Boolean;
begin
Result:=false;
BlockCleanStart:=0;
BlockCleanEnd:=0;
// scan code
BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
// read word at cursor
MoveCursorToCleanPos(CleanCursorPos);
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
dec(CurPos.StartPos);
MoveCursorToCleanPos(CurPos.StartPos);
ReadNextAtom;
BlockStartFound:=false;
repeat
ReadPriorAtom;
if (CurPos.StartPos<0) then begin
// start of source found -> this is always a block start
MoveCursorToCleanPos(1);
BlockStartFound:=true;
break;
end
else if Src[CurPos.StartPos] in [')',']','}'] then begin
// jump backward to matching bracket
if not ReadBackwardTilAnyBracketClose then exit;
end
else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
begin
// block start found
BlockStartFound:=true;
break;
end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('UNTIL') then
begin
// read backward till BEGIN, CASE, ASM, RECORD, REPEAT
ReadBackTilBlockEnd(true);
end;
until false;
if not BlockStartFound then exit;
BlockCleanStart:=CurPos.StartPos;
// read word at cursor
MoveCursorToCleanPos(BlockCleanStart);
if Src[CurPos.StartPos] in ['(','[','{'] then begin
// jump forward to matching bracket
ReadNextAtom;
if not ReadForwardTilAnyBracketClose then exit;
end else begin
if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
dec(CurPos.StartPos);
MoveCursorToCleanPos(CurPos.StartPos);
ReadNextAtom;
if CurPos.EndPos=CurPos.StartPos then exit;
// read till block keyword counterpart
if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
// read forward till END, FINALLY, EXCEPT
ReadTilBlockEnd(true,false);
end else
exit;
end;
BlockCleanEnd:=CurPos.StartPos;
Result:=true;
end;
function TStandardCodeTool.CompleteBlock(const CursorPos: TCodeXYPosition;
SourceChangeCache: TSourceChangeCache; OnlyIfCursorBlockIndented: boolean;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
{ For example:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
begin
|
...
something
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if then begin
|
...
something
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
begin
|
procedure
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Statements:
begin: end;
asm: end;
try: finally end;
finally: end;
except: end;
repeat: until ;
case of: end;
case :: ;
case else: end;
(: )
[: ]
Types:
(: )
[: ]
record: end;
class: end;
object: end;
interface: end;
}
type
TBlockType = (
btNone,
btBegin,
btAsm,
btEdgedBracket,
btRoundBracket,
btTry,
btFinally,
btExcept,
btCase,
btCaseOf,
btCaseColon,
btCaseElse,
btRepeat,
btIf,
btIfElse,
btClass,
btInterface,
btObject,
btRecord
);
TBlock = record
Typ: TBlockType;
StartPos: integer;
InnerIndent: integer;
InnerStartPos: integer;
end;
PBlock = ^TBlock;
TBlockStack = record
Stack: PBlock;
Capacity: integer;
Top: integer;
end;
TExBool = (ebNone, ebTrue, ebFalse);
var
CleanCursorPos: integer;
StartNode: TCodeTreeNode;
InternalCursorAtEmptyLine: TExBool;
Beauty: TBeautifyCodeOptions;
function CursorAtEmptyLine: Boolean;
// true if cursor in empty line or at line end in front of an empty line
var
p: LongInt;
begin
if InternalCursorAtEmptyLine=ebNone then begin
if (CleanCursorPos>SrcLen) or InEmptyLine(Src,CleanCursorPos) then
InternalCursorAtEmptyLine:=ebTrue
else begin
p:=CleanCursorPos;
while (p<=SrcLen) do begin
case Src[p] of
' ',#9: inc(p);
#10,#13:
begin
// after cursor the rest of the line is blank
// check the next line
inc(p);
if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
inc(p);
if (p>SrcLen) or InEmptyLine(Src,p) then
InternalCursorAtEmptyLine:=ebTrue
else
InternalCursorAtEmptyLine:=ebFalse;
break;
end;
else
InternalCursorAtEmptyLine:=ebFalse;
break;
end;
end;
end;
end;
Result:=InternalCursorAtEmptyLine=ebTrue;
end;
procedure InitStack(out Stack: TBlockStack);
begin
FillByte(Stack{%H-},SizeOf(Stack),0);
Stack.Top:=-1;
end;
procedure FreeStack(var Stack: TBlockStack);
begin
ReAllocMem(Stack.Stack,0);
Stack.Capacity:=0;
Stack.Top:=-1;
end;
procedure BeginBlock(var Stack: TBlockStack; Typ: TBlockType;
StartPos: integer);
var
Block: PBlock;
begin
inc(Stack.Top);
if Stack.Top>=Stack.Capacity then begin
if Stack.Capacity=0 then
Stack.Capacity:=16
else
Stack.Capacity:=Stack.Capacity*2;
ReAllocMem(Stack.Stack,SizeOf(TBlock)*Stack.Capacity);
end;
{$IFDEF VerboseCompleteBlock}
DebugLn([GetIndentStr(Stack.Top*2),'BeginBlock ',CleanPosToStr(StartPos),' ',GetAtom]);
{$ENDIF}
Block:=@Stack.Stack[Stack.Top];
Block^.Typ:=Typ;
Block^.StartPos:=StartPos;
Block^.InnerIndent:=-1;
Block^.InnerStartPos:=-1;
end;
procedure EndBlock(var Stack: TBlockStack);
begin
{$IFDEF VerboseCompleteBlock}
DebugLn([GetIndentStr(Stack.Top*2),'EndBlock ',GetAtom,' ',CleanPosToStr(CurPos.StartPos),', started at ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
{$ENDIF}
dec(Stack.Top);
end;
function TopBlockType(const Stack: TBlockStack): TBlockType;
begin
if Stack.Top>=0 then
Result:=Stack.Stack[Stack.Top].Typ
else
Result:=btNone;
end;
function Replace(NewCode: string; FromPos, ToPos, Indent: integer;
FrontGap, AfterGap: TGapTyp; BeautifyFlags: TBeautifyCodeFlags): boolean;
var
p: LongInt;
begin
Result:=false;
if NewCode='' then exit(true);
// try to avoid changing current line
if (FrontGap=gtEmptyLine) then begin
p:=FromPos;
while (p>1) and (Src[p-1] in [' ',#9]) do dec(p);
if (p=1) or (Src[p] in [#10,#13]) then begin
while (p<=SrcLen) and (Src[p] in [' ',#9]) do inc(p);
if (p>SrcLen) or (Src[p] in [#10,#13]) then begin
// inserting in an empty line
inc(p);
if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
inc(p);
FrontGap:=gtNewLine;
FromPos:=p;
if ToPos<FromPos then ToPos:=FromPos;
end;
end;
end;
// replace trailing spaces
while (ToPos<=SrcLen) and (Src[ToPos] in [' ',#9]) do inc(ToPos);
// use existing semicolon
if (NewCode[length(NewCode)]=';')
and (ToPos<=SrcLen) and (Src[ToPos]=';') then begin
AfterGap:=gtNone;
inc(ToPos);
end;
// use existing "else"
if (NewCode[length(NewCode)]=';') then begin
MoveCursorToCleanPos(ToPos);
ReadNextAtom;
if UpAtomIs('ELSE') then
NewCode:=copy(NewCode,1,length(NewCode)-1);
end;
// adjust indent of first line
if FrontGap in [gtNone,gtSpace] then begin
BeautifyFlags:=BeautifyFlags+[bcfDoNotIndentFirstLine];
NewCode:=Beauty.GetIndentStr(Indent-GetPosInLine(Src,FromPos))+NewCode;
end;
// beautify
NewCode:=Beauty.BeautifyStatement(
NewCode,Indent,BeautifyFlags);
if AfterGap=gtNewLine then begin
// do not reuse existing newline, but always add newline
NewCode:=NewCode+Beauty.LineEnd;
if (ToPos<SrcLen) and (not (Src[ToPos] in [#10,#13])) then
NewCode:=NewCode+Beauty.GetIndentStr(Beauty.GetLineIndent(Src,ToPos));
AfterGap:=gtNone;
end;
{$IFDEF VerboseCompleteBlock}
debugln(['Replace Indent=',Indent,' NewCode="',dbgstr(NewCode),'" Replace: InFront="',DbgStr(copy(Src,FromPos-15,15)),'",Replace="',dbgstr(copy(Src,FromPos,ToPos-FromPos)),'",Behind="',dbgstr(copy(Src,ToPos,15)),'" FrontGap=',dbgs(FrontGap),' AfterGap=',dbgs(AfterGap)]);
{$ENDIF}
// insert
if not SourceChangeCache.Replace(FrontGap,AfterGap,
FromPos,ToPos,NewCode) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function CompleteStatements(var Stack: TBlockStack): Boolean;
var
CursorBlockLvl: Integer; // the stack level of the cursor
LastPos: Integer;
LineStart: boolean; // Atom is first atom of a line in cursor block (not in sub block)
Indent: Integer;
CursorBlockInnerIndent, CursorBlockOuterIndent: LongInt;
CursorBlock: TBlock;
AtomInFrontOfCursor: TAtomPosition;
BehindCursorBlock: Boolean; // atom is behind cursor block
InCursorBlock: Boolean;
NeedCompletion: integer;
InsertPos: LongInt;
NewCode: String;
AfterGap: TGapTyp;
FrontGap: TGapTyp;
BeautifyFlags: TBeautifyCodeFlags;
BehindPos: LongInt;
CursorInEmptyStatement: Boolean;
FromPos: LongInt;
ToPos: LongInt;
WasInCursorBlock: Boolean;
function EndBlockIsOk: boolean;
begin
//DebugLn(['EndBlockIsOk ']);
if (NeedCompletion>0) and (CursorBlockLvl>=0)
and (Stack.Top=CursorBlockLvl)
and (Beauty.GetLineIndent(Src,CurPos.StartPos)=CursorBlockOuterIndent) then begin
// cursor block is properly closed => do not complete
{$IFDEF VerboseCompleteBlock}
debugln(['EndBlockIsOk cursor block is properly closed at ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
NeedCompletion:=0;
end;
EndBlock(Stack);
Result:=true;
if (not BehindCursorBlock) and (Stack.Top<CursorBlockLvl) then
BehindCursorBlock:=true;
end;
function CloseBrackets: boolean;
begin
while TopBlockType(Stack) in [btRoundBracket,btEdgedBracket] do begin
if not EndBlockIsOk then exit(false);
end;
Result:=true;
end;
function InsertPosAtCursor: integer;
begin
Result:=BasicCodeTools.FindLineEndOrCodeInFrontOfPosition(Src,
CurPos.StartPos,CleanCursorPos,Scanner.NestedComments);
end;
begin
Result:=false;
MoveCursorToNodeStart(StartNode);
CursorBlockLvl:=-2;
LastPos:=-1;
CursorBlockOuterIndent:=0;
CursorBlockInnerIndent:=0;
Indent:=0;
CursorBlock.StartPos:=0;
BehindCursorBlock:=false;
NeedCompletion:=0;
AtomInFrontOfCursor.StartPos:=0;
CursorInEmptyStatement:=false;
repeat
ReadNextAtom;
//DebugLn(['ReadStatements Atom=',GetAtom,' TopTyp=',ord(TopBlockType(Stack)),' Top=',Stack.Top]);
if (Stack.Top>=0) and (Stack.Stack[Stack.Top].InnerIndent<0)
and (not PositionsInSameLine(Src,Stack.Stack[Stack.Top].StartPos,CurPos.StartPos))
then begin
// the first atom of this block is on a new line
Stack.Stack[Stack.Top].InnerIndent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
Stack.Stack[Stack.Top].InnerStartPos:=CurPos.StartPos;
end;
// check if cursor reached
if (CurPos.StartPos>=CleanCursorPos) and (CursorBlockLvl<0) then begin
// reached cursor
CursorBlockLvl:=Stack.Top;
if CursorBlockLvl<0 then begin
// cursor outside blocks or on first atom of first block
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements no completion: cursor outside blocks or on first atom of first block ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
exit;
end else begin
CursorBlock:=Stack.Stack[CursorBlockLvl];
CursorBlockOuterIndent:=Beauty.GetLineIndent(Src,CursorBlock.StartPos);
CursorBlockInnerIndent:=Stack.Stack[Stack.Top].InnerIndent;
if (CursorBlockInnerIndent<=CursorBlockOuterIndent)
and OnlyIfCursorBlockIndented then begin
// cursor block not indented
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements no completion: cursor block not indented ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
{$ENDIF}
exit;
end;
AtomInFrontOfCursor:=LastAtoms.GetPriorAtom;
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements reached cursor: ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' LastAtom=',GetAtom(AtomInFrontOfCursor),' CurAtom=',GetAtom]);
{$ENDIF}
if (CurPos.Flag=cafSemicolon)
and ((AtomInFrontOfCursor.Flag=cafSemicolon)
or (CursorBlock.StartPos=AtomInFrontOfCursor.StartPos))
and (FindNextNonSpace(Src,AtomInFrontOfCursor.EndPos)=CurPos.StartPos)
then begin
// cursor in empty statement
CursorInEmptyStatement:=true;
end;
end;
//DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Indent=',CursorBlockIndent]);
end;
InCursorBlock:=(CursorBlockLvl>=0) and (CursorBlockLvl=Stack.Top)
and (not BehindCursorBlock);
WasInCursorBlock:=InCursorBlock;
// check if end of node
if (CurPos.StartPos>SrcLen) or (CurPos.StartPos>=StartNode.EndPos) then
begin
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: source end found at ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end;
break;
end;
// check if line start vs outer indent
LineStart:=InCursorBlock and (LastPos>0)
and not PositionsInSameLine(Src,LastPos,CurPos.StartPos);
if LineStart then
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
if LineStart and (NeedCompletion=0) then begin
// atom is in same block as cursor (not sub block)
// and first atom of a line
// => check indent
//debugln(['CompleteStatements first atom of line in cursor block: ',GetAtom,' Indent=',Indent,' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
if (Indent=CursorBlockOuterIndent) then begin
if (CursorBlockLvl>0)
and (Stack.Stack[CursorBlockLvl-1].InnerIndent=Indent)
and (Stack.Stack[CursorBlockLvl-1].InnerStartPos<CurPos.StartPos)
then begin
{ for example:
Code;
begin|
Code;
}
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: between same indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent,' Parent.InnerStartPos=',CleanPosToStr(Stack.Stack[CursorBlockLvl-1].InnerStartPos)]);
{$ENDIF}
NeedCompletion:=InsertPosAtCursor;
end;
end else if (Indent<CursorBlockOuterIndent) then begin
// for example:
// begin
// Code;
// |end;
//DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorAtEmptyLine=',CursorAtEmptyLine,' CursorInEmptyStatement=',CursorInEmptyStatement]);
if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
// for example:
// begin
// Code;
// |end;
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: at out indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
{$ENDIF}
NeedCompletion:=InsertPosAtCursor;
end else if CursorAtEmptyLine or CursorInEmptyStatement
or (FindNextNonSpace(Src,CleanCursorPos)=CurPos.StartPos) then begin
{ for example:
begin
|
Code;
end;
begin
Code;
|
end;
}
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: at empty line ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end else begin
{ It needs completion, but where?
for example:
begin
begin|
Code;
end;
}
end;
end;
end;
// check block starts/ends
case CurPos.Flag of
cafEnd:
if (CurPos.EndPos<=SrcLen) and (Src[CurPos.EndPos]='.') then begin
{ end. of source found
The parsing started in a begin block, valid cases:
program a;
begin|
end.
implementation
begin|
end.
}
if (Stack.Top=0) and (TopBlockType(Stack)=btBegin)
and (StartNode.Desc=ctnBeginBlock)
and ((StartNode.Parent=nil)
or (StartNode.Parent.Desc in AllSourceTypes+[ctnInterface,ctnImplementation]))
then begin
if not EndBlockIsOk then exit; // close main begin
end else begin
// unexpected end of source
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end;
end;
break;
end else begin
case TopBlockType(Stack) of
btCaseOf,btCaseElse:
begin
if not EndBlockIsOk then exit; // close btCaseOf,btCaseElse
if not EndBlockIsOk then exit; // close btCase
end;
btBegin,btFinally,btExcept,btCase:
if not EndBlockIsOk then exit;
btCaseColon,btRepeat:
begin
// missing semicolon or until
DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end;
break;
end;
btTry:
begin
// missing finally/except
DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end;
break;
end;
btAsm:
if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]<>'@') then begin
if not EndBlockIsOk then exit;
end;
else
// missing begin
exit;
end;
end;
cafEdgedBracketOpen:
BeginBlock(Stack,btEdgedBracket,CurPos.StartPos);
cafEdgedBracketClose:
if TopBlockType(Stack)=btEdgedBracket then begin
if not EndBlockIsOk then exit;
end else begin
// missing [
exit;
end;
cafRoundBracketOpen:
BeginBlock(Stack,btRoundBracket,CurPos.StartPos);
cafRoundBracketClose:
if TopBlockType(Stack)=btRoundBracket then begin
if not EndBlockIsOk then exit;
end else begin
// missing (
exit;
end;
cafColon:
if TopBlockType(Stack)=btCaseOf then
BeginBlock(Stack,btCaseColon,CurPos.StartPos);
cafSemicolon:
while TopBlockType(Stack)
in [btCaseColon,btIf,btIfElse,btRoundBracket,btEdgedBracket] do begin
if not EndBlockIsOk then exit;
end;
cafWord:
if TopBlockType(Stack)<>btAsm then begin
if UpAtomIs('BEGIN') then
BeginBlock(Stack,btBegin,CurPos.StartPos)
else if UpAtomIs('TRY') then
BeginBlock(Stack,btTry,CurPos.StartPos)
else if UpAtomIs('FINALLY') then begin
if TopBlockType(Stack)=btTry then
if not EndBlockIsOk then exit;
BeginBlock(Stack,btFinally,CurPos.StartPos)
end else if UpAtomIs('EXCEPT') then begin
if TopBlockType(Stack)=btTry then
if not EndBlockIsOk then exit;
BeginBlock(Stack,btExcept,CurPos.StartPos)
end else if UpAtomIs('REPEAT') then
BeginBlock(Stack,btRepeat,CurPos.StartPos)
else if UpAtomIs('UNTIL') then begin
if TopBlockType(Stack)=btRepeat then begin
if not EndBlockIsOk then exit;
end else begin
// until without repeat
DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock,' Block=',ord(TopBlockType(Stack))]);
DebugLn(['ReadStatements unexpected until at ',CleanPosToStr(CurPos.StartPos)]);
exit;
end;
end else if UpAtomIs('ASM') then begin
BeginBlock(Stack,btAsm,CurPos.StartPos);
end else if UpAtomIs('IF') then begin
BeginBlock(Stack,btIf,CurPos.StartPos);
end else if UpAtomIs('THEN') then begin
CloseBrackets;
if TopBlockType(Stack)=btIf then begin
Stack.Stack[Stack.Top].InnerIndent:=-1;
Stack.Stack[Stack.Top].InnerStartPos:=-1;
end;
end else if UpAtomIs('CASE') then begin
BeginBlock(Stack,btCase,CurPos.StartPos)
end else if UpAtomIs('OF') then begin
CloseBrackets;
if TopBlockType(Stack)=btCase then
BeginBlock(Stack,btCaseOf,CurPos.StartPos);
end else if UpAtomIs('ELSE') then begin
CloseBrackets;
case TopBlockType(Stack) of
btIf:
begin
if not EndBlockIsOk then exit;
BeginBlock(Stack,btIfElse,CurPos.StartPos);
end;
btCaseOf:
begin
if not EndBlockIsOk then exit;
BeginBlock(Stack,btCaseElse,CurPos.StartPos);
end;
btBegin:
begin
// missing end
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing end. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
{$ENDIF}
NeedCompletion:=InsertPosAtCursor;
end;
break;
end;
btCaseColon,btRepeat:
begin
// missing semicolon
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
{$ENDIF}
NeedCompletion:=InsertPosAtCursor;
end;
break;
end;
end;
end else if UpAtomIs('PROCEDURE') or UpAtomIs('FUNCTION')
or UpAtomIs('CONSTRUCTOR') or UpAtomIs('DESTRUCTOR')
or UpAtomIs('VAR') or UpAtomIs('TYPE') or UpAtomIs('CONST')
or UpAtomIs('RESOURCESTRING') or UpAtomIs('LABEL') or UpAtomIs('CLASS')
or UpAtomIs('INITIALIZATION') or UpAtomIs('FINALIZATION')
then begin
// unexpected keyword => block not closed
if InCursorBlock and (NeedCompletion=0) then begin
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: unexpected keyword ',GetAtom,' at ',CleanPosToStr(CurPos.StartPos)]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
end;
break;
end;
end;
end;
// check if line start
if LineStart and WasInCursorBlock and (not BehindCursorBlock) then begin
// atom is first atom of a line
// and atom is in same block as cursor (not sub block)
// (maybe the atom started a new sub block, but it did not close it)
// => check indent
//debugln(['CompleteStatements ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
if (Indent<CursorBlockInnerIndent) and (NeedCompletion=0) then begin
if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
// for example:
// begin
// Code;
// |
// Code;
// Code;
//DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockIndent=',CursorBlockIndent]);
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
{$ENDIF}
NeedCompletion:=InsertPosAtCursor;
end else begin
// for example:
// begin
// |
// Code;
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
{$ENDIF}
NeedCompletion:=CleanCursorPos;
// Note: if the end is coming later, NeedCompletion is disabled
end;
end;
end;
LastPos:=CurPos.StartPos;
until Stack.Top<0;
{$IFDEF VerboseCompleteBlock}
DebugLn(['ReadStatements END Stack.Top=',Stack.Top,' CursorBlockLvl=',CursorBlockLvl,' BehindCursorBlock=',BehindCursorBlock]);
{$ENDIF}
if Stack.Top<0 then begin
// all blocks closed
{$IFDEF VerboseCompleteBlock}
if NeedCompletion>0 then
DebugLn(['ReadStatements all blocks closed: no completion needed']);
{$ENDIF}
NeedCompletion:=0;
end;
if (NeedCompletion>0) then begin
InsertPos:=NeedCompletion;
while (InsertPos>CleanCursorPos) and (IsSpaceChar[Src[InsertPos-1]]) do
dec(InsertPos);
Indent:=CursorBlockOuterIndent;
// check code behind
BehindPos:=FindNextNonSpace(Src,InsertPos);
if BehindPos<=SrcLen then begin
if (not CursorInEmptyStatement)
and PositionsInSameLine(Src,InsertPos,BehindPos) then begin
// target line not empty
{$IFDEF VerboseCompleteBlock}
DebugLn(['CompleteStatements target line not empty => skip']);
{$ENDIF}
exit;
end;
if (Beauty.GetLineIndent(Src,BehindPos)>Indent) then begin
// code behind is more indented
// for example
// repeat
// |
// DoSomething;
debugln(['CompleteStatements BehindPos ',dbgstr(copy(Src,BehindPos-8,8)),'|',dbgstr(copy(Src,BehindPos,8))]);
{$IFDEF VerboseCompleteBlock}
DebugLn(['CompleteStatements code behind is indented more (Behind=',Beauty.GetLineIndent(Src,BehindPos),' > Indent=',Indent,') => skip']);
{$ENDIF}
exit;
end;
end;
NewCode:=';';
FrontGap:=gtEmptyLine;
AfterGap:=gtNewLine;
FromPos:=InsertPos;
ToPos:=InsertPos;
BeautifyFlags:=[bcfIndentExistingLineBreaks];
if CursorInEmptyStatement and (BehindPos<=SrcLen) then begin
// replace the empty statement
FrontGap:=gtNewLine;
ToPos:=BehindPos;
end;
case CursorBlock.Typ of
btBegin,btFinally,btExcept,btAsm,btCaseOf,btCaseElse:
NewCode:='end'+NewCode;
btRepeat:
NewCode:='until '+NewCode;
btTry:
NewCode:='finally'+SourceChangeCache.BeautifyCodeOptions.LineEnd
+'end'+NewCode;
btCaseColon:
begin
FrontGap:=gtNone;
AfterGap:=gtNone;
end;
else
exit;
end;
if (CursorBlockLvl=0) and (AfterGap=gtNewLine) then begin
// top level => insert empty lines between top level structures
AfterGap:=gtEmptyLine;
end;
if not Replace(NewCode,FromPos,ToPos,Indent,FrontGap,AfterGap,
BeautifyFlags) then exit;
end;
Result:=true;
end;
function CompleteClassSection: Boolean;
{ type
TMyClass = class
|
}
var
LastIndent: LongInt;
Indent: LongInt;
InsertPos: LongInt;
NeedCompletion: Integer;
begin
Result:=false;
if CleanCursorPos<StartNode.StartPos then exit;
LastIndent:=Beauty.GetLineIndent(Src,StartNode.Parent.StartPos);
MoveCursorToNodeStart(StartNode);
//debugln(['CompleteClassSection ',dbgstr(copy(Src,StartNode.StartPos-10,10)),'|',dbgstr(copy(Src,StartNode.StartPos,10))]);
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
if Indent<LastIndent then
LastIndent:=Indent;
ReadNextAtom;
NeedCompletion:=0;
if (CurPos.StartPos>SrcLen) then begin
{ For example:
TMyClass = class
<EOF>
}
NeedCompletion:=CleanCursorPos;
end else if CurPos.Flag=cafWord then begin
if AtomIsIdentifier then begin
ReadNextAtom;
if CurPos.Flag=cafEqual then begin
{ For example:
TMyClass = class
TIdentifier =
}
NeedCompletion:=CleanCursorPos;
end else
exit(true);
end else begin
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
if Indent<LastIndent then begin
{ For example:
TMyClass = class
type
}
NeedCompletion:=CleanCursorPos;
end;
end;
end else
exit(true);
//debugln(['CompleteClassSection NeedCompletion=',NeedCompletion]);
if NeedCompletion>0 then begin
InsertPos:=NeedCompletion;
Result:=Replace('end;',InsertPos,InsertPos,LastIndent,
gtNewLine,gtEmptyLine,
[bcfIndentExistingLineBreaks]);
end else
Result:=true;
end;
function CompleteClassInterface: Boolean;
{ type
TMyClass = interface
|
}
var
LastIndent: LongInt;
Indent: LongInt;
InsertPos: LongInt;
begin
Result:=false;
if CleanCursorPos<StartNode.StartPos then exit;
LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
MoveCursorToNodeStart(StartNode);
ReadNextAtom;
if CleanCursorPos<CurPos.EndPos then exit(true);
ReadNextAtom;
if CurPos.Flag=cafEnd then exit(true);
if CleanCursorPos<=CurPos.StartPos then begin
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
InsertPos:=CleanCursorPos;
if Indent<LastIndent then begin
if not Replace('end;',InsertPos,InsertPos,LastIndent,
gtNewLine,gtEmptyLine,
[bcfIndentExistingLineBreaks])
then
exit;
end;
end;
Result:=true;
end;
function CompleteRecord: Boolean;
{ type
TMyClass = record
|
}
var
LastIndent: LongInt;
Indent: LongInt;
InsertPos: LongInt;
begin
Result:=false;
if CleanCursorPos<StartNode.StartPos then exit;
LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
MoveCursorToNodeStart(StartNode);
ReadNextAtom; // record
if CleanCursorPos<CurPos.EndPos then exit(true);
ReadNextAtom;
if CurPos.Flag=cafEnd then exit(true);
if CleanCursorPos<=CurPos.StartPos then begin
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
InsertPos:=CleanCursorPos;
if Indent<=LastIndent then begin
if not Replace('end;',InsertPos,InsertPos,LastIndent,
gtNewLine,gtEmptyLine,
[bcfIndentExistingLineBreaks])
then
exit;
end;
end;
Result:=true;
end;
var
Stack: TBlockStack;
CommentStart, CommentEnd: integer;
begin
Result:=false;
NewPos:=CursorPos;
NewTopLine:=-1;
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
StartNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
if CleanPosIsInComment(CleanCursorPos,StartNode.StartPos,CommentStart,CommentEnd) then begin
{$IFDEF VerboseCompleteBlock}
debugln(['TStandardCodeTool.CompleteBlock cursor (',CursorPos.Y,',',CursorPos.X,') cleanpos=[',CleanPosToStr(CleanCursorPos),'] in comment Start=[',CleanPosToStr(CommentStart),'] End=[',CleanPosToStr(CommentEnd),']']);
{$ENDIF}
exit;
end;
InternalCursorAtEmptyLine:=ebNone;
SourceChangeCache.MainScanner:=Scanner;
Beauty:=SourceChangeCache.BeautifyCodeOptions;
InitStack(Stack);
try
{$IFDEF VerboseCompleteBlock}
DebugLn(['TStandardCodeTool.CompleteBlock ',StartNode.DescAsString]);
{$ENDIF}
if StartNode.Desc in AllPascalStatements then begin
while (StartNode.Parent<>nil)
and (StartNode.Parent.Desc in AllPascalStatements) do
StartNode:=StartNode.Parent;
if not CompleteStatements(Stack) then exit;
end
else if (StartNode.Desc in AllClassSections)
or ((StartNode.Desc in AllClassSubSections) and (StartNode.Parent.Desc in AllClassSections))
then begin
if not CompleteClassSection then exit;
end
else if StartNode.Desc in AllClassInterfaces then begin
if not CompleteClassInterface then exit;
end
else if StartNode.Desc=ctnRecordType then begin
if not CompleteRecord then exit;
end;
finally
FreeStack(Stack);
end;
Result:=true;
end;
{$IFDEF GuessMisplacedIfdef}
function TStandardCodeTool.GuessMisplacedIfdefEndif(
const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var
StartCursorPos, EndCursorPos: integer;
StartCode, EndCode: Pointer;
begin
Result:=false;
try
BeginParsing(lsrEnd);
except
// ignore scanner and parser errors
on e: ELinkScannerError do ;
on e: ECodeToolError do ;
end;
if Scanner<>nil then begin
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,StartCursorPos);
StartCode:=CursorPos.Code;
Result:=Scanner.GuessMisplacedIfdefEndif(StartCursorPos,StartCode,
EndCursorPos,EndCode);
if Result then begin
NewPos.Code:=TCodeBuffer(EndCode);
NewPos.Code.AbsoluteToLineCol(EndCursorPos,NewPos.Y,NewPos.X);
if JumpSingleLinePos>0 then begin
NewTopLine:=NewPos.Y-(VisibleEditorLines*JumpSingleLinePos div 100);
if NewTopLine<1 then NewTopLine:=1;
end else
NewTopLine:=NewPos.Y;
end;
end;
end;
{$ENDIF}
function TStandardCodeTool.FindEnclosingIncludeDirective(
const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
NewTopLine: integer): boolean;
var
CleanCursorPos, LinkIndex, NewCleanPos: integer;
begin
Result:=false;
NewPos:=CleanCodeXYPosition;
NewTopLine:=-1;
try
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
LinkIndex:=Scanner.LinkIndexAtCleanPos(CleanCursorPos);
LinkIndex:=Scanner.FindParentLink(LinkIndex);
if LinkIndex<0 then
// this is no include file
exit;
NewPos.Code:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
// calculate the directive end bracket
NewCleanPos:=Scanner.Links[LinkIndex].CleanedPos+Scanner.LinkSize(LinkIndex)-1;
Result:=CleanPosToCaretAndTopLine(NewCleanPos,NewPos,NewTopLine);
finally
ClearIgnoreErrorAfter;
end;
end;
function TStandardCodeTool.FindModeDirective(DoBuildTree: boolean;
out ACleanPos: integer): boolean;
var
ParamPos: Integer;
begin
Result:=false;
ACleanPos:=0;
if DoBuildTree then BuildTree(lsrMainUsesSectionStart);
ACleanPos:=FindNextCompilerDirectiveWithName(Src,1,'Mode',
Scanner.NestedComments,ParamPos);
if ParamPos=0 then ;
Result:=(ACleanPos>0) and (ACleanPos<=SrcLen);
end;
function TStandardCodeTool.FindResourceDirective(DoBuildTree: boolean;
var ACleanPos: integer; const Filename: string): boolean;
var
ParamPos: Integer;
FilenameStartPos: Integer;
FilenameEndPos: LongInt;
begin
Result:=false;
if DoBuildTree then BuildTree(lsrEnd);
ACleanPos:=1;
repeat
ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
Scanner.NestedComments,ParamPos);
if ParamPos=0 then ;
if (ACleanPos<1) or (ACleanPos>SrcLen) then
exit(false);
if Filename='' then begin
// searching any filename -> found
exit(true);
end;
FilenameStartPos:=ACleanPos+length('{$R ');
FilenameEndPos:=FilenameStartPos;
while (FilenameEndPos<=SrcLen) and (Src[FilenameEndPos]<>'}') do
inc(FilenameEndPos);
if CompareText(PChar(Pointer(Filename)),length(Filename),
@Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
true,false)=0
then begin
// filename found
exit(true);
end;
ACleanPos:=FilenameEndPos+1;
until ACleanPos>SrcLen;
end;
function TStandardCodeTool.FindResourceDirective(
const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition;
out NewTopLine: integer; const Filename: string): boolean;
var
CleanCursorPos: integer;
begin
Result:=false;
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
if not FindResourceDirective(false,CleanCursorPos,Filename) then begin
//DebugLn('TStandardCodeTool.FindResourceDirective resource directive not found');
exit;
end;
Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
end;
function TStandardCodeTool.AddResourceDirective(const Filename: string;
SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
var
ANode: TCodeTreeNode;
Indent: LongInt;
InsertPos: Integer;
AddSrc: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
BuildTree(lsrEnd);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// find an insert position
ANode:=FindImplementationNode;
if ANode<>nil then begin
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=ANode.StartPos+length('implementation');
end else begin
ANode:=FindMainBeginEndNode;
if ANode<>nil then begin
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=ANode.StartPos;
end else begin
ANode:=FindMainUsesNode;
if ANode<>nil then begin
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
InsertPos:=ANode.StartPos;
end else begin
Indent:=0;
InsertPos:=1;
end;
end;
end;
// insert directive
SourceChangeCache.MainScanner:=Scanner;
if NewSrc<>'' then
AddSrc:=NewSrc
else
AddSrc:=Beauty.GetIndentStr(Indent)+'{$R '+Filename+'}';
if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
AddSrc) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.FindIncludeDirective(DoBuildTree: boolean;
var ACleanPos: integer; const Filename: string): boolean;
var
FilenameStartPos: Integer;
FilenameEndPos: LongInt;
CommentStart: integer;
CommentEnd: integer;
begin
Result:=false;
if DoBuildTree then BuildTree(lsrEnd);
ACleanPos:=1;
repeat
ACleanPos:=FindNextIncludeDirective(Src,ACleanPos,Scanner.NestedComments,
FilenameStartPos,FilenameEndPos,CommentStart,CommentEnd);
if (ACleanPos<1) or (ACleanPos>SrcLen) then
exit(false);
if Filename='' then begin
// searching any filename -> found
exit(true);
end;
if CompareText(PChar(Pointer(Filename)),length(Filename),
@Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
true,false)=0
then begin
// filename found
exit(true);
end;
ACleanPos:=FilenameEndPos+1;
until ACleanPos>SrcLen;
end;
function TStandardCodeTool.FindIncludeDirective(
const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
NewTopLine: integer; const Filename: string): boolean;
var
CleanCursorPos: integer;
begin
Result:=false;
BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
if not FindIncludeDirective(false,CleanCursorPos,Filename) then begin
//DebugLn('TStandardCodeTool.FindIncludeDirective resource directive not found');
exit;
end;
Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
end;
function TStandardCodeTool.AddIncludeDirectiveForInit(const Filename: string;
SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
var
ANode: TCodeTreeNode;
Indent: LongInt;
InsertPos: Integer;
AddSrc: String;
Beauty: TBeautifyCodeOptions;
begin
Result:=false;
BuildTree(lsrEnd);
Beauty:=SourceChangeCache.BeautifyCodeOptions;
// find an insert position
ANode:=FindInitializationNode;
if ANode<>nil then begin
Indent:=Beauty.GetLineIndent(Src,ANode.StartPos)+Beauty.Indent;
InsertPos:=ANode.StartPos+length('initialization');
end else begin
ANode:=FindMainBeginEndNode;
if ANode<>nil then begin
MoveCursorToNodeStart(ANode);
ReadNextAtom;
//debugln(['TStandardCodeTool.AddIncludeDirective ',GetAtom]);
Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos)+Beauty.Indent;
InsertPos:=CurPos.EndPos;
end else begin
debugln(['TStandardCodeTool.AddIncludeDirective ToDo: add initialization / begin..end']);
exit;
end;
end;
// insert directive
SourceChangeCache.MainScanner:=Scanner;
if NewSrc<>'' then
AddSrc:=NewSrc
else
AddSrc:=Beauty.GetIndentStr(Indent)+'{$I '+Filename+'}';
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
AddSrc) then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.AddUnitWarnDirective(WarnID, Comment: string;
TurnOn: boolean; SourceChangeCache: TSourceChangeCache): boolean;
const
DirectiveFlagValue: array[boolean] of string = ('off','on');
var
ACleanPos, DirEndPos, InsertStartPos, MaxPos: Integer;
Node: TCodeTreeNode;
p, IDStartPos, IDEndPos, ParamPos: PChar;
NewCode: String;
begin
Result:=false;
if WarnID='' then
raise Exception.Create('TStandardCodeTool.AddUnitWarnDirective missing WarnID');
InsertStartPos:=0;
BuildTree(lsrMainUsesSectionStart);
SourceChangeCache.MainScanner:=Scanner;
// fix comment
if Comment<>'' then begin
for ACleanPos:=1 to length(Comment) do
if Comment[ACleanPos] in [#0..#8,#11,#12,#14..#31,'{','}'] then
Comment[ACleanPos]:='?';
if not (Comment[1] in [' ',#9,#10,#13]) then Comment:=' '+Comment;
end;
// insert in front of first node after source name
Node:=Tree.Root;
MaxPos:=Node.StartPos;
if Node.Desc in AllSourceTypes then
Node:=Node.Next;
if (Node<>nil) and (Node.Desc=ctnSrcName) then begin
MaxPos:=Node.EndPos;
Node:=Node.NextSkipChilds;
end;
if Node<>nil then
MaxPos:=Node.StartPos;
MaxPos:=FindLineEndOrCodeAfterPosition(MaxPos,true,true);
// find existing directive for replacement
ACleanPos:=1;
repeat
ACleanPos:=FindNextCompilerDirective(Src,ACleanPos,Scanner.NestedComments);
if (ACleanPos<1) or (ACleanPos>MaxPos) then
break;
DirEndPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments)+1;
p:=@Src[ACleanPos+2];
if CompareIdentifiers(p,'warn')=0 then begin
IDStartPos:=p+4;
while IDStartPos^ in [' ',#9,#10,#13] do
inc(IDStartPos);
IDEndPos:=IDStartPos;
while IDEndPos^ in ['0'..'9','A'..'Z','a'..'z','_'] do
inc(IDEndPos);
if CompareText(PChar(Pointer(WarnID)),length(WarnID),
IDStartPos,IDEndPos-IDStartPos,false)=0
then begin
// warn directive found
p:=IDEndPos;
while p^ in [' ',#9,#10,#13] do
inc(p);
ParamPos:=p;
while p^ in ['+','-','a'..'z','A'..'Z'] do
inc(p);
if not SourceChangeCache.Replace(gtSpace,gtNone,
ParamPos-PChar(Src)+1,p-PChar(Src)+1,DirectiveFlagValue[TurnOn])
then
exit;
Result:=SourceChangeCache.Apply;
exit;
end;
end else if (CompareIdentifiers(p,'i')=0) or (CompareIdentifiers(p,'include')=0)
then begin
// insert before include file
if MaxPos>ACleanPos then MaxPos:=ACleanPos;
break;
end;
ACleanPos:=DirEndPos;
until ACleanPos>MaxPos;
// there was no such directive yet -> find nice insert pos
InsertStartPos:=FindLineEndOrCodeInFrontOfPosition(MaxPos,true,true);
NewCode:='{$WARN '+WarnID+' '+DirectiveFlagValue[TurnOn]+Comment+'}';
if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
InsertStartPos,InsertStartPos,NewCode)
then
exit;
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.FixIncludeFilenames(Code: TCodeBuffer;
SourceChangeCache: TSourceChangeCache;
out FoundIncludeFiles: TStrings;
var MissingIncludeFilesCodeXYPos: TFPList): boolean;
var
ASource: String;
{procedure WriteMissingIncludeFilesCodeXYPos;
var
CodePos: PCodeXYPosition;
i: Integer;
begin
if MissingIncludeFilesCodeXYPos<>nil then begin
for i:=0 to MissingIncludeFilesCodeXYPos.Count-1 do begin
CodePos:=PCodeXYPosition(MissingIncludeFilesCodeXYPos[i]);
DebugLn('TStandardCodeTool.FixMissingUnits ',dbgs(CodePos));
DebugLn('TStandardCodeTool.FixMissingUnits ',CodePos^.Code.Filename);
debugln(CodePos^.Code.Filename
+'('+IntToStr(CodePos^.y)+','+IntToStr(CodePos^.x)+')'
+' missing include file');
end;
end;
end;}
procedure Add(FilenameSrcPos: integer; const AFilename: string; Found: boolean);
var
NewFilename: String;
p: PCodeXYPosition;
begin
if Found then begin
if FoundIncludeFiles=nil then
FoundIncludeFiles:=TStringListUTF8Fast.Create;
NewFilename:=TrimFilename(AFilename);
if FoundIncludeFiles.IndexOf(NewFilename)<0 then
FoundIncludeFiles.Add(NewFilename);
end else begin
if MissingIncludeFilesCodeXYPos=nil then
MissingIncludeFilesCodeXYPos:=TFPList.Create;
New(p);
p^.Code:=Code;
Code.AbsoluteToLineCol(FilenameSrcPos,p^.y,p^.x);
MissingIncludeFilesCodeXYPos.Add(p);
///DebugLn('TStandardCodeTool.FixIncludeFilenames.Add "',p^.Code.Filename,'" ',dbgs(p),' X=',dbgs(p^.X),' Y=',dbgs(p^.Y));
//WriteMissingIncludeFilesCodeXYPos;
end;
end;
function SearchIncludeFilename(FilenameSrcPos: integer; const AFilename: string): string;
var
AFilePath: String;
BaseDir: String;
CurFilename: String;
IncludePath: String;
PathDivider: String;
ACodeBuf: TCodeBuffer;
begin
Result:=TrimFilename(AFilename);
if FilenameIsAbsolute(Result) then begin
Result:=DirectoryCache.Pool.FindDiskFilename(Result,true);
Add(FilenameSrcPos,Result,FileExistsCached(Result));
//DebugLn('SearchIncludeFilename AbsoluteFilename="',Result,'"');
end else begin
BaseDir:=ExtractFilePath(MainFilename);
//DebugLn('SearchIncludeFilename BaseDir="',BaseDir,'"');
if FilenameIsAbsolute(BaseDir) then begin
// unit has normal path -> not virtual
AFilePath:=ExtractFilePath(Result);
if AFilePath<>'' then begin
// search relative to unit
//debugln(['SearchIncludeFilename BaseDir+Result=',BaseDir+Result]);
CurFilename:=DirectoryCache.Pool.FindDiskFilename(BaseDir+Result,true);
//debugln(['SearchIncludeFilename DiskFilename=',CurFilename]);
Result:=CreateRelativePath(CurFilename,BaseDir);
//debugln(['SearchIncludeFilename RelativeDiskFilename=',Result]);
if FileExistsCached(CurFilename) then
Add(FilenameSrcPos,CurFilename,true)
else
Add(FilenameSrcPos,Result,false);
//DebugLn('SearchIncludeFilename relative filename="',CurFilename,'"');
end else begin
// search in path
IncludePath:='';
PathDivider:=':;';
if (Scanner.Values<>nil) then begin
IncludePath:=Scanner.Values.Variables[ExternalMacroStart+'INCPATH'];
if Scanner.Values.IsDefined('DELPHI') then
PathDivider:=':'
end;
CurFilename:=SearchFileInPath(Result,BaseDir,IncludePath,PathDivider, ctsfcAllCase);
if CurFilename<>'' then begin
// found
Result:=CreateRelativePath(CurFilename,BaseDir);
Add(FilenameSrcPos,CurFilename,true);
end else begin
// not found
Add(FilenameSrcPos,Result,false);
end;
//DebugLn('SearchIncludeFilename search in include path="',IncludePath,'" Result="',Result,'"');
end;
end else begin
// unit has no path -> virtual unit -> search in virtual files
ACodeBuf:=TCodeBuffer(Scanner.LoadSourceCaseLoUp(Result));
if ACodeBuf<>nil then begin
Result:=ACodeBuf.Filename;
Add(FilenameSrcPos,Result,true);
end else begin
Add(FilenameSrcPos,Result,false);
end;
end;
end;
end;
procedure FixFilename(StartPos, EndPos: integer);
var
OldFilename: String;
AFilename: String;
begin
OldFilename:=GetForcedPathDelims(copy(ASource,StartPos,EndPos-StartPos));
//DebugLn('FixFilename ',dbgs(StartPos),' ',dbgs(EndPos),' ',OldFilename);
AFilename:=OldFilename;
if ExtractFileExt(AFilename)='' then begin
// add default extension
if (Scanner.CompilerMode=cmDELPHI) then
AFilename:=AFilename+'.pas'
else
AFilename:=AFilename+'.pp';
end;
AFilename:=SearchIncludeFilename(StartPos,AFilename);
if OldFilename<>AFilename then begin
DebugLn('TStandardCodeTool.FixIncludeFilenames.FixFilename replacing in '
+Code.Filename+' include directive "',OldFilename,'" with "',AFilename,'"');
SourceChangeCache.ReplaceEx(gtNone,gtNone,0,0,Code,StartPos,EndPos,AFilename);
end;
end;
var
p: Integer;
NestedComments: Boolean;
FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos: integer;
begin
Result:=false;
FoundIncludeFiles:=nil;
if (Scanner=nil) or (Scanner.MainCode=nil) then exit;
ASource:=Code.Source;
Scanner.Scan(lsrInit,false);
SourceChangeCache.MainScanner:=Scanner;
Result:=true;
NestedComments:=Scanner.NestedComments;
p:=1;
repeat
p:=BasicCodeTools.FindNextIncludeDirective(ASource,p,NestedComments,
FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos);
if (p<1) or (p>length(ASource)) then break;
if (CommentStartPos=0) and (CommentEndPos=0) then ;
FixFilename(FilenameStartPos,FilenameEndPos);
p:=FindCommentEnd(ASource,p,NestedComments);
//DebugLn('TStandardCodeTool.FixIncludeFilenames ',dbgs(p));
until false;
//WriteMissingIncludeFilesCodeXYPos;
Result:=SourceChangeCache.Apply;
end;
function TStandardCodeTool.ReadTilGuessedUnclosedBlock(
MinCleanPos: integer; ReadOnlyOneBlock: boolean): boolean;
// returns true if unclosed block found
var BlockType, CurBlockWord: TBlockKeyword;
BlockStart: integer;
begin
Result:=false;
BlockType:=bkwNone;
BlockStart:=-1;
// read til this block is closed
while (CurPos.StartPos<=SrcLen) do begin
if BlockKeywordFuncList.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
begin
for CurBlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
if UpAtomIs(BlockKeywords[CurBlockWord]) then
break;
if (CurBlockWord=bkwInterface) and (not LastAtomIs(0,'=')) then
CurBlockWord:=bkwNone;
if (CurBlockWord=bkwEnd) then begin
ReadNextAtom;
if AtomIsChar('.') then begin
// source end found
if BlockType in [bkwBegin,bkwNone] then begin
MoveCursorToCleanPos(SrcLen+1);
exit;
end else begin
MoveCursorToCleanPos(BlockStart);
Result:=true;
exit;
end;
end else
UndoReadNextAtom;
end;
if BlockType=bkwNone then begin
case CurBlockWord of
bkwBegin, bkwAsm, bkwRepeat, bkwCase, bkwTry, bkwRecord:
begin
BlockType:=CurBlockWord;
BlockStart:=CurPos.StartPos;
end;
bkwClass, bkwObject, bkwInterface, bkwDispInterface:
begin
ReadNextAtom;
if AtomIsChar(';') then begin
// forward class
end else if ((CurBlockWord=bkwClass) and UpAtomIs('OF')) then begin
// 'class of'
end else if ((CurBlockWord=bkwClass)
and (UpAtomIs('FUNCTION') or UpAtomIs('PROCEDURE'))) then begin
// 'class procedure'
end else if ((CurBlockWord=bkwObject) and LastUpAtomIs(0,'OF')) then
begin
// or 'of object'
end else begin
BlockType:=CurBlockWord;
BlockStart:=LastAtoms.GetPriorAtom.StartPos;
// read ancestor list class(...)
if CurPos.Flag=cafRoundBracketOpen then begin
repeat
ReadNextAtom;
if AtomIsIdentifier then begin
ReadNextAtom;
if CurPos.Flag=cafPoint then begin
ReadNextAtom;
AtomIsIdentifierE;
end;
end;
if CurPos.Flag=cafRoundBracketClose then break;
if CurPos.Flag<>cafComma then begin
exit(false);
end;
until false;
ReadNextAtom;
end;
// a semicolon directly behind the ancestor list ends the class
if (CurPos.Flag in [cafEnd,cafSemicolon]) then begin
// class ends
BlockType:=bkwNone;
end else begin
// class continues
UndoReadNextAtom;
end;
end;
end;
bkwEnd, bkwUntil:
begin
// close block keywords found, but no block was opened
// -> unclosed block found
Result:=true;
exit;
end;
end;
end
else
if ((BlockType in [bkwBegin, bkwAsm, bkwCase, bkwRecord, bkwClass,
bkwObject, bkwFinally, bkwExcept, bkwInterface, bkwDispInterface])
and (CurBlockWord=bkwEnd))
or ((BlockType=bkwRepeat) and (CurBlockWord=bkwUntil)) then begin
// block end found
if (MinCleanPos<=CurPos.StartPos)
and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
then begin
// different indent -> unclosed block found
if Beautifier.GetLineIndent(Src,BlockStart)>Beautifier.GetLineIndent(Src,CurPos.StartPos)
then begin
// the current block is more indented than the next block
// -> probably the current block misses a block end
MoveCursorToCleanPos(BlockStart);
end;
Result:=true;
exit;
end;
// end block
if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
// the 'end' keyword is the end for the case block and the record block
UndoReadNextAtom;
end;
BlockType:=bkwNone;
if ReadOnlyOneBlock then break;
end
else
if (BlockType=bkwTry) and (CurBlockWord in [bkwFinally,bkwExcept]) then
begin
// try..finally, try..except found
if (MinCleanPos<=CurPos.StartPos)
and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
then begin
// different indent -> unclosed block found
// probably a block start is missing, so the error position is
// here at block end
Result:=true;
exit;
end;
// change blocktype
BlockType:=CurBlockWord;
BlockStart:=CurPos.StartPos;
end
else
if ((BlockType in [bkwBegin,bkwRepeat,bkwTry,bkwFinally,bkwExcept,
bkwCase])
and (CurBlockWord in [bkwBegin,bkwRepeat,bkwTry,bkwCase,bkwAsm]))
or ((BlockType in [bkwClass,bkwInterface,bkwDispInterface,bkwObject,
bkwRecord])
and (CurBlockWord in [bkwRecord])) then
begin
// sub blockstart found -> read recursively
Result:=ReadTilGuessedUnclosedBlock(MinCleanPos,true);
if Result then exit;
end
else
if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
// variant record
end
else
if (BlockType=bkwClass) and (CurBlockWord=bkwClass) then begin
// class method
end
else
begin
// unexpected keyword found
if Beautifier.GetLineIndent(Src,BlockStart)>=Beautifier.GetLineIndent(Src,CurPos.StartPos)
then begin
// the current block is more or equal indented than the next block
// -> probably the current block misses a block end
MoveCursorToCleanPos(BlockStart);
end;
Result:=true;
exit;
end;
end;
ReadNextAtom;
end;
end;
function TStandardCodeTool.RemoveApplicationScaledStatement(
SourceChangeCache: TSourceChangeCache): boolean;
begin
Result := RemoveApplicationStatement('SCALED', SourceChangeCache);
end;
function TStandardCodeTool.RemoveApplicationStatement(const APropertyUpCase: string;
SourceChangeCache: TSourceChangeCache): boolean;
var
StartPos, ConstStartPos, EndPos: integer;
OldExists: Boolean;
FromPos: Integer;
ToPos: Integer;
begin
Result:=false;
// search old Application.XYZ:= statement
OldExists:=FindApplicationStatement(APropertyUpCase,StartPos,ConstStartPos,EndPos);
if not OldExists then
exit(true);
// -> delete whole line
FromPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);
ToPos:=FindLineEndOrCodeAfterPosition(EndPos);
SourceChangeCache.MainScanner:=Scanner;
if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
if not SourceChangeCache.Apply then exit;
Result:=true;
end;
function TStandardCodeTool.ReadForwardTilAnyBracketClose: boolean;
// this function reads any bracket
// (the ReadTilBracketClose function reads only brackets in code, not comments)
var OpenBracket: char;
begin
Result:=false;
OpenBracket:=Src[CurPos.StartPos];
if OpenBracket='{' then begin
// read til end of comment
MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
Result:=CurPos.StartPos<=SrcLen;
end else if OpenBracket='(' then begin
if (CurPos.StartPos<SrcLen) and (Src[CurPos.StartPos+1]='*') then begin
// read til end of comment
MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
Result:=CurPos.StartPos<=SrcLen;
end else begin
// round bracket operator
Result:=ReadTilBracketClose(false);
end;
end else if OpenBracket='[' then begin
Result:=ReadTilBracketClose(false);
end;
end;
function TStandardCodeTool.ReadBackwardTilAnyBracketClose: boolean;
// this function reads any bracket
// (the ReadBackTilBracketClose function reads only brackets in code,
// not comments)
var OpenBracket: char;
CommentLvl: integer;
begin
Result:=false;
OpenBracket:=Src[CurPos.StartPos];
if OpenBracket='}' then begin
// read backwards til end of comment
dec(CurPos.StartPos);
if (CurPos.StartPos>0) and (Src[CurPos.StartPos]=#3) then begin
// codetools skip comment
dec(CurPos.StartPos);
while (CurPos.StartPos>=1) do begin
if (Src[CurPos.StartPos]=#3) and (CurPos.StartPos>1)
and (Src[CurPos.StartPos-1]='}') then begin
dec(CurPos.StartPos,2);
break;
end;
dec(CurPos.StartPos);
end;
end else begin
// pascal comment
CommentLvl:=1;
while (CurPos.StartPos>=1) and (CommentLvl>0) do begin
case Src[CurPos.StartPos] of
'}': if Scanner.NestedComments then inc(CommentLvl);
'{':
if CommentLvl=1 then begin
Result:=true;
break;
end else
dec(CommentLvl);
end;
dec(CurPos.StartPos);
end;
end;
end else if OpenBracket=')' then begin
if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='*') then begin
// read til end of comment
dec(CurPos.StartPos,3);
while true do begin
if (CurPos.StartPos>=1)
and ((Src[CurPos.StartPos+1]='*') and (Src[CurPos.StartPos]='(')) then
begin
Result:=true;
exit;
end;
dec(CurPos.StartPos);
end;
end else begin
Result:=ReadBackTilBracketOpen(false);
end;
end else if OpenBracket=']' then begin
Result:=ReadBackTilBracketOpen(false);
end;
end;
function TStandardCodeTool.Explore(WithStatements: boolean;
Range: TLinkScannerRange): boolean;
var
Node: TCodeTreeNode;
begin
Result:=true;
BuildTree(Range);
Node:=Tree.Root;
while Node<>nil do begin
case Node.Desc of
ctnProcedure,ctnProcedureHead:
BuildSubTreeForProcHead(Node);
ctnBeginBlock:
if WithStatements then
BuildSubTreeForBeginBlock(Node);
ctnImplementation:
if ord(Range)<ord(lsrImplementationStart) then exit;
end;
Node:=Node.Next;
end;
end;
function TStandardCodeTool.Explore(WithStatements: boolean;
OnlyInterface: boolean): boolean;
begin
if OnlyInterface then
Result:=Explore(WithStatements,lsrImplementationStart)
else
Result:=Explore(WithStatements,lsrEnd);
end;
finalization
FreeAndNil(BlockKeywordFuncList);
end.