1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     TStandardCodeTool enhances TIdentCompletionTool with many standard code
25     editing functions for the following categories:
26       - source name
27       - uses sections
28       - lazarus resources
29       - Application.CreateForm statements
30       - published variables
31       - resource strings
32       - compiler and IDE directives
33       - code exploring
34       - code blocks
35 }
36 unit StdCodeTools;
37 
38 {$ifdef FPC}{$mode objfpc}{$endif}{$H+}
39 
40 interface
41 
42 {$I codetools.inc}
43 
44 { $DEFINE VerboseGetStringConstBounds}
45 { $DEFINE VerboseCompleteBlock}
46 { $DEFINE VerboseCheckLFM}
47 { $DEFINE VerboseFindUnusedUnits}
48 
49 uses
50   {$IFDEF MEM_CHECK}
51   MemCheck,
52   {$ENDIF}
53   Classes, SysUtils, TypInfo, Laz_AVL_Tree,
54   // Codetools
55   CodeToolsStrConsts, FileProcs, CodeTree, CodeAtom,
56   FindDeclarationTool, IdentCompletionTool, PascalReaderTool, PascalParserTool,
57   ExprEval, KeywordFuncLists, BasicCodeTools, LinkScanner,
58   CodeCache, LFMTrees, SourceChanger, CustomCodeTool, CodeToolsStructs,
59   // LazUtils
60   LazFileUtils, LazFileCache, LazUTF8, AvgLvlTree;
61 
62 type
63   TStandardCodeTool = class;
64 
65   TInsertStatementPosDescription = class
66   public
67     InsertPos: integer;
68     Indent: integer;
69     CodeXYPos: TCodeXYPosition;
70     FrontGap, AfterGap: TGapTyp;
71     Description: string;
72   end;
73 
74   TUsesSection = (usMain, usImplementation);
75   TAddUsesFlag = (
76     aufLast,
77     aufNotCheckSpecialUnit
78     );
79   TAddUsesFlags = set of TAddUsesFlag;
80 
81   TOnFindDefinePropertyForContext = procedure(Sender: TObject;
82     const ClassContext, AncestorClassContext: TFindContext;
83     LFMNode: TLFMTreeNode;
84     const IdentName: string; var IsDefined: boolean) of object;
oolnull85   TOnIDEDirectiveFilter = function(Tool: TStandardCodeTool;
86     StartPos, EndPos: integer): boolean of object; // true = use
87 
88   { TStandardCodeTool }
89 
90   TStandardCodeTool = class(TIdentCompletionTool)
91   private
ReadTilGuessedUnclosedBlocknull92     function ReadTilGuessedUnclosedBlock(MinCleanPos: integer;
93       ReadOnlyOneBlock: boolean): boolean;
ReadForwardTilAnyBracketClosenull94     function ReadForwardTilAnyBracketClose: boolean;
ReadBackwardTilAnyBracketClosenull95     function ReadBackwardTilAnyBracketClose: boolean;
FindApplicationStatementnull96     function FindApplicationStatement(const APropertyUpCase: string;
97           out StartPos, ConstStartPos, EndPos: integer): boolean;
SetApplicationStatementnull98     function SetApplicationStatement(const APropertyName, NewCode: string;
99           SourceChangeCache: TSourceChangeCache): boolean;
RemoveApplicationStatementnull100     function RemoveApplicationStatement(const APropertyUpCase: string;
101           SourceChangeCache: TSourceChangeCache): boolean;
102   public
103     // explore the code
Explorenull104     function Explore(WithStatements: boolean; Range: TLinkScannerRange): boolean;
Explorenull105     function Explore(WithStatements: boolean;
106           OnlyInterface: boolean = false): boolean;
107 
108     // source name  e.g. 'unit UnitName;'
GetCachedSourceNamenull109     function GetCachedSourceName: string;
RenameSourcenull110     function RenameSource(const NewName: string;
111           SourceChangeCache: TSourceChangeCache): boolean;
112 
113     // uses sections
RenameUsedUnitnull114     function RenameUsedUnit(const OldUnitName, NewUnitName, NewUnitInFile: string;
115           SourceChangeCache: TSourceChangeCache): boolean;
ReplaceUsedUnitsnull116     function ReplaceUsedUnits(UnitNamePairs: TStringToStringTree; // ToDo: dotted
117           SourceChangeCache: TSourceChangeCache): boolean;
AddUnitToUsesSectionnull118     function AddUnitToUsesSection(UsesNode: TCodeTreeNode;
119           const NewUnitName, NewUnitInFile: string;
120           SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
AddUnitToSpecificUsesSectionnull121     function AddUnitToSpecificUsesSection(UsesSection: TUsesSection;
122           const NewUnitName, NewUnitInFile: string;
123           SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
AddUnitToMainUsesSectionnull124     function AddUnitToMainUsesSection(const NewUnitName, NewUnitInFile: string;
125           SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
AddUnitToImplementationUsesSectionnull126     function AddUnitToImplementationUsesSection(const NewUnitName, NewUnitInFile: string;
127           SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
UnitExistsInUsesSectionnull128     function UnitExistsInUsesSection(UsesSection: TUsesSection;
129           const AnUnitName: string): boolean;
UnitExistsInUsesSectionnull130     function UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
131                                 const AnUnitName: string): boolean;
RemoveUnitFromUsesSectionnull132     function RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
133                                 const AnUnitName: string;
134                                 SourceChangeCache: TSourceChangeCache): boolean;
RemoveUnitFromAllUsesSectionsnull135     function RemoveUnitFromAllUsesSections(const AnUnitName: string;
136                                 SourceChangeCache: TSourceChangeCache): boolean;
FixUsedUnitCasenull137     function FixUsedUnitCase(SourceChangeCache: TSourceChangeCache): boolean;
FixUsedUnitCaseInUsesSectionnull138     function FixUsedUnitCaseInUsesSection(UsesNode: TCodeTreeNode;
139                                 SourceChangeCache: TSourceChangeCache): boolean;
FindUsedUnitNamesnull140     function FindUsedUnitNames(var MainUsesSection,
141                                ImplementationUsesSection: TStrings): boolean;
FindUsedUnitNamesnull142     function FindUsedUnitNames(var List: TStringToStringTree): boolean;
FindUsedUnitFilesnull143     function FindUsedUnitFiles(var MainUsesSection: TStrings): boolean;
FindUsedUnitFilesnull144     function FindUsedUnitFiles(var MainUsesSection,
145                                ImplementationUsesSection: TStrings): boolean;
FindDelphiProjectUnitsnull146     function FindDelphiProjectUnits(out FoundInUnits, MissingInUnits,
147                                     NormalUnits: TStrings;
148                                     UseContainsSection: boolean = false;
149                                     IgnoreNormalUnits: boolean = false): boolean;
UsesSectionToFilenamesnull150     function UsesSectionToFilenames(UsesNode: TCodeTreeNode): TStrings;
UsesSectionToUnitnamesnull151     function UsesSectionToUnitnames(UsesNode: TCodeTreeNode): TStrings;
FindMissingUnitsnull152     function FindMissingUnits(var MissingUnits: TStrings; FixCase: boolean;
153                               SearchImplementation: boolean;
154                               SourceChangeCache: TSourceChangeCache): boolean;
CommentUnitsInUsesSectionnull155     function CommentUnitsInUsesSection(MissingUnits: TStrings;
156       SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
CommentUnitsInUsesSectionsnull157     function CommentUnitsInUsesSections(MissingUnits: TStrings;
158                                 SourceChangeCache: TSourceChangeCache): boolean;
FindUnusedUnitsnull159     function FindUnusedUnits(Units: TStrings): boolean;
160 
161     // lazarus resources
FindNextIncludeInInitializationnull162     function FindNextIncludeInInitialization(
163           var LinkIndex: integer): TCodeBuffer;
FindLazarusResourceInBuffernull164     function FindLazarusResourceInBuffer(ResourceCode: TCodeBuffer;
165           const ResourceName: string): TAtomPosition;
FindLazarusResourcenull166     function FindLazarusResource(const ResourceName: string): TAtomPosition;
AddLazarusResourcenull167     function AddLazarusResource(ResourceCode: TCodeBuffer;
168           const ResourceName, ResourceData: string;
169           SourceChangeCache: TSourceChangeCache): boolean;
RemoveLazarusResourcenull170     function RemoveLazarusResource(ResourceCode: TCodeBuffer;
171           const ResourceName: string;
172           SourceChangeCache: TSourceChangeCache): boolean;
RenameIncludenull173     function RenameInclude(LinkIndex: integer; const NewFilename: string;
174           KeepPath: boolean;
175           SourceChangeCache: TSourceChangeCache): boolean;
CheckLFMnull176     function CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
177           const OnFindDefineProperty: TOnFindDefinePropertyForContext;
178           RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
179           ObjectsMustExist: boolean): boolean;
180 
181     // Application.Createform statements
FindCreateFormStatementnull182     function FindCreateFormStatement(StartPos: integer;
183           const AClassName, AVarName: string;
184           out Position: TAtomPosition): integer; // 0=found, -1=not found, 1=found, but wrong classname
AddCreateFormStatementnull185     function AddCreateFormStatement(const AClassName, AVarName: string;
186           SourceChangeCache: TSourceChangeCache): boolean;
RemoveCreateFormStatementnull187     function RemoveCreateFormStatement(const AVarName: string;
188           SourceChangeCache: TSourceChangeCache): boolean;
ChangeCreateFormStatementnull189     function ChangeCreateFormStatement(StartPos: integer;
190           const OldClassName, OldVarName: string;
191           const NewClassName, NewVarName: string;
192           OnlyIfExists: boolean;
193           SourceChangeCache: TSourceChangeCache): boolean;
ListAllCreateFormStatementsnull194     function ListAllCreateFormStatements: TStrings;
SetAllCreateFromStatementsnull195     function SetAllCreateFromStatements(List: TStrings;
196           SourceChangeCache: TSourceChangeCache): boolean;
197 
198     // Application.Title:=<string const> statements
FindApplicationTitleStatementnull199     function FindApplicationTitleStatement(out StartPos, StringConstStartPos,
200           EndPos: integer): boolean;
GetApplicationTitleStatementnull201     function GetApplicationTitleStatement(StringConstStartPos, EndPos: integer;
202           var Title: string): boolean;
SetApplicationTitleStatementnull203     function SetApplicationTitleStatement(const NewTitle: string;
204           SourceChangeCache: TSourceChangeCache): boolean;
RemoveApplicationTitleStatementnull205     function RemoveApplicationTitleStatement(
206           SourceChangeCache: TSourceChangeCache): boolean;
207 
208     // Application.Scaled:=<boolean const> statements
FindApplicationScaledStatementnull209     function FindApplicationScaledStatement(out StartPos, BooleanConstStartPos,
210           EndPos: integer): boolean;
GetApplicationScaledStatementnull211     function GetApplicationScaledStatement(BooleanConstStartPos, EndPos: integer;
212           var AScaled: boolean): boolean;
SetApplicationScaledStatementnull213     function SetApplicationScaledStatement(const NewScaled: boolean;
214           SourceChangeCache: TSourceChangeCache): boolean;
RemoveApplicationScaledStatementnull215     function RemoveApplicationScaledStatement(
216           SourceChangeCache: TSourceChangeCache): boolean;
217 
218     // forms
RenameFormnull219     function RenameForm(const OldFormName, OldFormClassName: string;
220           const NewFormName, NewFormClassName: string;
221           SourceChangeCache: TSourceChangeCache): boolean;
FindFormAncestornull222     function FindFormAncestor(const AClassName: string;
223           out AncestorClassName: string): boolean;
224 
225     // published variables
FindPublishedVariablenull226     function FindPublishedVariable(const AClassName, AVarName: string;
227           ExceptionOnClassNotFound: boolean): TCodeTreeNode;
AddPublishedVariablenull228     function AddPublishedVariable(const AClassName,VarName, VarType: string;
229           SourceChangeCache: TSourceChangeCache): boolean; virtual;
RemovePublishedVariablenull230     function RemovePublishedVariable(const AClassName, AVarName: string;
231           ExceptionOnClassNotFound: boolean;
232           SourceChangeCache: TSourceChangeCache): boolean;
RenamePublishedVariablenull233     function RenamePublishedVariable(const AClassName,
234           AOldVarName: string; const NewVarName, VarType: shortstring;
235           ExceptionOnClassNotFound: boolean;
236           SourceChangeCache: TSourceChangeCache): boolean;
GatherPublishedClassElementsnull237     function GatherPublishedClassElements(const TheClassName: string;
238           ExceptionOnClassNotFound, WithVariables, WithMethods,
239           WithProperties, WithAncestors: boolean;
240           out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
RetypeClassVariablesnull241     function RetypeClassVariables(const AClassName: string;
242           ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
243           SourceChangeCache: TSourceChangeCache;
244           SearchImplementationToo: boolean = false): boolean;
FindDanglingComponentEventsnull245     function FindDanglingComponentEvents(const TheClassName: string;
246           RootComponent: TComponent; ExceptionOnClassNotFound,
247           SearchInAncestors: boolean;
248           out ListOfPInstancePropInfo: TFPList;
249           const OverrideGetMethodName: TOnGetMethodname = nil): boolean;
250 
251     // variables, constants, types
RemoveIdentifierDefinitionnull252     function RemoveIdentifierDefinition(const CursorPos: TCodeXYPosition;
253           SourceChangeCache: TSourceChangeCache): boolean;
254 
InsertStatementsnull255     function InsertStatements(InsertPos: TInsertStatementPosDescription;
256           Statements: string; SourceChangeCache: TSourceChangeCache): boolean;
InsertStatementsnull257     function InsertStatements(CleanPos: integer;
258           Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
259           SourceChangeCache: TSourceChangeCache): boolean;
260 
261     // blocks (e.g. begin..end)
FindBlockCounterPartnull262     function FindBlockCounterPart(const CursorPos: TCodeXYPosition;
263           out NewPos: TCodeXYPosition; out NewTopLine: integer;
264           IncludeCloseToken: Boolean = false): boolean;
FindBlockStartnull265     function FindBlockStart(const CursorPos: TCodeXYPosition;
266           out NewPos: TCodeXYPosition; out NewTopLine: integer;
267           SkipStart: boolean = true): boolean;
GuessUnclosedBlocknull268     function GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
269           out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
FindBlockCleanBoundsnull270     function FindBlockCleanBounds(const CursorPos: TCodeXYPosition;
271           out BlockCleanStart, BlockCleanEnd: integer): boolean;
CompleteBlocknull272     function CompleteBlock(const CursorPos: TCodeXYPosition;
273           SourceChangeCache: TSourceChangeCache;
274           OnlyIfCursorBlockIndented: boolean;
275           out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
276 
277     // compiler directives
278     {$IFDEF GuessMisplacedIfdef}
GuessMisplacedIfdefEndifnull279     function GuessMisplacedIfdefEndif(const CursorPos: TCodeXYPosition;
280           out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
281     {$ENDIF}
FindEnclosingIncludeDirectivenull282     function FindEnclosingIncludeDirective(const CursorPos: TCodeXYPosition;
283           out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
FindModeDirectivenull284     function FindModeDirective(DoBuildTree: boolean;
285           out ACleanPos: integer): boolean;
FindResourceDirectivenull286     function FindResourceDirective(DoBuildTree: boolean;
287           var ACleanPos: integer; const Filename: string = ''): boolean;
FindResourceDirectivenull288     function FindResourceDirective(const CursorPos: TCodeXYPosition;
289           out NewPos: TCodeXYPosition; out NewTopLine: integer;
290           const Filename: string = ''): boolean;
AddResourceDirectivenull291     function AddResourceDirective(const Filename: string;
292           SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
293           ): boolean;
FindIncludeDirectivenull294     function FindIncludeDirective(DoBuildTree: boolean;
295           var ACleanPos: integer; const Filename: string = ''): boolean;
FindIncludeDirectivenull296     function FindIncludeDirective(const CursorPos: TCodeXYPosition;
297           out NewPos: TCodeXYPosition; out NewTopLine: integer;
298           const Filename: string = ''): boolean;
AddIncludeDirectiveForInitnull299     function AddIncludeDirectiveForInit(const Filename: string;
300           SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
301           ): boolean;
AddUnitWarnDirectivenull302     function AddUnitWarnDirective(WarnID, Comment: string; TurnOn: boolean;
303           SourceChangeCache: TSourceChangeCache): boolean;
FixIncludeFilenamesnull304     function FixIncludeFilenames(Code: TCodeBuffer;
305           SourceChangeCache: TSourceChangeCache;
306           out FoundIncludeFiles: TStrings;
307           var MissingIncludeFilesCodeXYPos: TFPList): boolean;
308 
309     // search & replace
ReplaceWordsnull310     function ReplaceWords(IdentList: TStrings; ChangeStrings: boolean;
311           SourceChangeCache: TSourceChangeCache;
312           SkipPointWords: boolean = false): boolean;
FindNearestIdentifierNodenull313     function FindNearestIdentifierNode(const CursorPos: TCodeXYPosition;
314           IdentTree: TAVLTree): TAVLTreeNode;
ReplaceWordnull315     function ReplaceWord(const OldWord, NewWord: string; ChangeStrings: boolean;
316           SourceChangeCache: TSourceChangeCache;
317           SkipPointWords: boolean = false): boolean;
318 
319     // comments
CommentCodenull320     function CommentCode(const StartPos, EndPos: integer;
321           SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;
322 
323     // expressions
GetStringConstBoundsnull324     function GetStringConstBounds(const CursorPos: TCodeXYPosition;
325           out StartPos, EndPos: TCodeXYPosition;
326           ResolveComments: boolean): boolean;
GetStringConstAsFormatStringnull327     function GetStringConstAsFormatString(StartPos, EndPos: integer;
328           out FormatStringConstant, FormatParameters: string;
329           out StartInStringConst, EndInStringConst: boolean): boolean;
GetStringConstAsFormatStringnull330     function GetStringConstAsFormatString(StartPos, EndPos: integer;
331           out FormatStringConstant, FormatParameters: string): boolean;
ExtractOperandnull332     function ExtractOperand(const CursorPos: TCodeXYPosition;
333           out Operand: string; WithPostTokens, WithAsOperator,
334           WithoutTrailingPoints: boolean): boolean;
335 
336     // resource strings
GatherResourceStringSectionsnull337     function GatherResourceStringSections(const CursorPos: TCodeXYPosition;
338           PositionList: TCodeXYPositions): boolean;
IdentifierExistsInResourceStringSectionnull339     function IdentifierExistsInResourceStringSection(
340           const CursorPos: TCodeXYPosition;
341           const ResStrIdentifier: string): boolean;
GatherResourceStringsWithValuenull342     function GatherResourceStringsWithValue(const CursorPos: TCodeXYPosition;
343           const StringValue: string;
344           PositionList: TCodeXYPositions): boolean;
GatherResourceStringIdentsnull345     function GatherResourceStringIdents(const SectionPos: TCodeXYPosition;
346           var IdentTree: TAVLTree): boolean;
FindNearestResourceStringnull347     function FindNearestResourceString(const CursorPos,
348           SectionPos: TCodeXYPosition;
349           var NearestPos: TCodeXYPosition): boolean;
AddResourceStringnull350     function AddResourceString(const SectionPos: TCodeXYPosition;
351           const NewIdentifier, NewValue: string;
352           InsertPolicy: TResourcestringInsertPolicy;
353           const NearestPos: TCodeXYPosition;
354           SourceChangeCache: TSourceChangeCache): boolean;
CreateIdentifierFromStringConstnull355     function CreateIdentifierFromStringConst(
356           const StartCursorPos, EndCursorPos: TCodeXYPosition;
357           out Identifier: string; MaxLen: integer): boolean;
StringConstToFormatStringnull358     function StringConstToFormatString(
359           const StartCursorPos, EndCursorPos: TCodeXYPosition;
360           out FormatStringConstant,FormatParameters: string;
361           out StartInStringConst, EndInStringConst: boolean): boolean;
362 
363     // register procedure
364     function HasInterfaceRegisterProc: boolean;
365 
366     // Delphi to Lazarus conversion
ConvertDelphiToLazarusSourcenull367     function ConvertDelphiToLazarusSource(AddLRSCode: boolean;
368           SourceChangeCache: TSourceChangeCache): boolean;
369 
370     // IDE % directives
GetIDEDirectivesnull371     function GetIDEDirectives(DirectiveList: TStrings;
372           const Filter: TOnIDEDirectiveFilter = nil): boolean;
SetIDEDirectivesnull373     function SetIDEDirectives(DirectiveList: TStrings;
374           SourceChangeCache: TSourceChangeCache;
375           const Filter: TOnIDEDirectiveFilter = nil): boolean;
376 
377     procedure CalcMemSize(Stats: TCTMemStats); override;
378   end;
379 
380 
381 implementation
382 
383 
384 type
385   TBlockKeyword = (bkwNone, bkwBegin, bkwAsm, bkwTry, bkwCase, bkwRepeat,
386                    bkwRecord, bkwClass, bkwObject, bkwInterface,
387                    bkwDispInterface, bkwEnd, bkwUntil, bkwFinally,
388                    bkwExcept);
389 
390 const
391   BlockKeywords: array[TBlockKeyword] of string = (
392       '(unknown)', 'BEGIN', 'ASM', 'TRY', 'CASE', 'REPEAT', 'RECORD', 'CLASS',
393       'OBJECT', 'INTERFACE', 'DISPINTERFACE', 'END', 'UNTIL', 'FINALLY',
394       'EXCEPT'
395     );
396 
397 var
398   BlockKeywordFuncList: TKeyWordFunctionList;
399 
400 procedure BuildBlockKeyWordFuncList;
401 var BlockWord: TBlockKeyword;
402 begin
403   if BlockKeywordFuncList=nil then begin
404     BlockKeywordFuncList:=TKeyWordFunctionList.Create('StdCodeTools.BlockKeywordFuncList');
405     for BlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
406       with BlockKeywordFuncList do
407         Add(BlockKeywords[BlockWord],{$ifdef FPC}@{$endif}AllwaysTrue);
408   end;
409 end;
410 
411 
412 { TStandardCodeTool }
413 
414 {-------------------------------------------------------------------------------
415   function TStandardCodeTool.GetCachedSourceName: string;
416   Params: none
417   Result: the source name (= e.g. the identifier behind 'program'/'unit' keyword)
418 
419   This function does neither check if source needs reparsing, nor does it check
420   for errors in code. It simple checks if there is a first node, which is
421   typically the source type and name.
422   This function can therefore be used as a fast GetSourceName function.
423 -------------------------------------------------------------------------------}
GetCachedSourceNamenull424 function TStandardCodeTool.GetCachedSourceName: string;
425 begin
426   Result:=GetSourceName(false);
427 end;
428 
RenameSourcenull429 function TStandardCodeTool.RenameSource(const NewName: string;
430   SourceChangeCache: TSourceChangeCache): boolean;
431 var
432   NamePos: TAtomPosition;
433 begin
434   Result:=false;
435   BuildTree(lsrSourceName);
436   //debugln(['TStandardCodeTool.RenameSource NewName=',NewName]);
437   if (not GetSourceNamePos(NamePos)) or (NamePos.StartPos<1) or (NewName='')
438   or (Length(NewName)>255) then exit;
439   //debugln(['TStandardCodeTool.RenameSource OldName="',dbgstr(copy(Src,NamePos.StartPos,NamePos.EndPos-NamePos.StartPos)),'"']);
440   SourceChangeCache.MainScanner:=Scanner;
441   SourceChangeCache.Replace(gtNone,gtNone,NamePos.StartPos,NamePos.EndPos,NewName);
442   if not SourceChangeCache.Apply then exit;
443   CachedSourceName:=NewName;
444   Result:=true;
445 end;
446 
TStandardCodeTool.RenameUsedUnitnull447 function TStandardCodeTool.RenameUsedUnit(const OldUnitName, NewUnitName,
448   NewUnitInFile: string; SourceChangeCache: TSourceChangeCache): boolean;
449 var
450   UnitPos, InPos: TAtomPosition;
451   NewUsesTerm: string;
452 begin
453   Result:=false;
454   if (not IsDottedIdentifier(OldUnitName))
455   or (not IsDottedIdentifier(NewUnitName)) then
456     exit;
457   if not FindUnitInAllUsesSections(OldUnitName,UnitPos,InPos) then begin
458     //debugln('TStandardCodeTool.RenameUsedUnit not found: ',OldUnitName,' ');
459     exit;
460   end;
461   SourceChangeCache.MainScanner:=Scanner;
462   if InPos.StartPos>0 then
463     UnitPos.EndPos:=InPos.EndPos;
464   // build use unit term
465   NewUsesTerm:=NewUnitName;
466   if NewUnitInFile<>'' then
467     NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+'''';
468   // Note: do not use beautifier, unit names are case sensitive
469   if ReplacementNeedsLineEnd(Src,UnitPos.StartPos,UnitPos.EndPos,
470     length(NewUsesTerm),SourceChangeCache.BeautifyCodeOptions.LineLength) then
471   begin
472     if not SourceChangeCache.Replace(gtNewLine,gtNone,
473       UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
474   end else begin
475     if not SourceChangeCache.Replace(gtSpace,gtNone,
476       UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
477   end;
478   if not SourceChangeCache.Apply then exit;
479   Result:=true;
480 end;
481 
ReplaceUsedUnitsnull482 function TStandardCodeTool.ReplaceUsedUnits(UnitNamePairs: TStringToStringTree;
483   SourceChangeCache: TSourceChangeCache): boolean;
484 var
485   ExistingUnits: TStringToStringTree;
486 
487   procedure CleanNewUnits(const AnUnitName: string; var NewText: string);
488   var
489     StartPos: Integer;
490     EndPos: LongInt;
491     CommaBehind: LongInt;
492     CommaInFront: Integer;
493     NewUnitName: String;
494   begin
495     // remove all units, that already exists
496     StartPos:=1;
497     CommaInFront:=-1;
498     while StartPos<=length(NewText) do begin
499       EndPos:=StartPos;
500       while (EndPos<=length(NewText)) and (IsIdentChar[NewText[EndPos]]) do
501         inc(EndPos);
502       if EndPos<=StartPos then break;
503       NewUnitName:=copy(NewText,StartPos,EndPos-StartPos);
504       // set EndPos to start of next unit
505       CommaBehind:=-1;
506       while (EndPos<=length(NewText)) do begin
507         if NewText[EndPos]='''' then begin
508           inc(EndPos);
509           while (EndPos<=length(NewText)) and (NewText[EndPos]<>'''') do
510             inc(EndPos);
511         end else if NewText[EndPos]=',' then begin
512           CommaBehind:=EndPos;
513           while (EndPos<=length(NewText))
514           and (not IsIdentStartChar[NewText[EndPos]]) do
515             inc(EndPos);
516           break;
517         end;
518         inc(EndPos);
519       end;
520       if (SysUtils.CompareText(AnUnitName,NewUnitName)=0) then begin
521         // this is the old unit or
522         //DebugLn('Replace: keep old unit "',NewUnitName,'"');
523       end else if ExistingUnits.Contains(NewUnitName) then begin
524         // this unit already exists and should not be added
525         //DebugLn('Replace: already exists: "',NewUnitName,'"="',ExistingUnits[NewUnitName],'" CommaInFront=',dbgs(CommaInFront),' CommaBehind=',dbgs(CommaBehind));
526         if CommaBehind>0 then
527           System.Delete(NewText,StartPos,EndPos-StartPos)
528         else if CommaInFront>0 then
529           System.Delete(NewText,CommaInFront,EndPos-CommaInFront)
530         else
531           System.Delete(NewText,StartPos,EndPos-StartPos);
532         EndPos:=StartPos;
533         CommaBehind:=-1;
534       end else begin
535         // this unit does not exist yet
536         //DebugLn('Replace new unit with "',NewUnitName,'"');
537       end;
538       if CommaBehind>0 then
539         CommaInFront:=CommaBehind;
540       StartPos:=EndPos;
541     end;
542   end;
543 
Replacenull544   function Replace(UsesNode: TCodeTreeNode): boolean;
545   var
546     UnitNameAtom: TAtomPosition;
547     InAtom: TAtomPosition;
548     NewText: string;
549     CommaInFront: LongInt;
550     FromPos: LongInt;
551     ToPos: LongInt;
552     CommaBehind: Integer;
553     AnUnitName: String;
554   begin
555     if UsesNode=nil then exit(true);
556     MoveCursorToUsesStart(UsesNode);
557     CommaInFront:=-1;
558     repeat
559       // read next unit name
560       ReadNextUsedUnit(UnitNameAtom, InAtom);
561       if CurPos.Flag=cafComma then
562         CommaBehind:=CurPos.StartPos
563       else
564         CommaBehind:=-1;
565       AnUnitName:=GetAtom(UnitNameAtom);
566       if UnitNamePairs.Contains(AnUnitName) then begin
567         // replace
568         NewText:=UnitNamePairs[AnUnitName];
569         //DebugLn('Replace Unit="',AnUnitName,'" NewText="',NewText,'"');
570 
571         CleanNewUnits(AnUnitName,NewText);
572 
573         if NewText='' then begin
574           // comment unit
575           if CommaInFront>0 then begin
576             // example:  uses a{, b};
577             FromPos:=CommaInFront;
578             ToPos:=UnitNameAtom.EndPos;
579             if InAtom.StartPos>0 then
580               ToPos:=InAtom.EndPos;
581           end else if CommaBehind>0 then begin
582             // example:  uses {a,} b;
583             //           uses {a,} {b};
584             FromPos:=UnitNameAtom.StartPos;
585             ToPos:=CommaBehind+1;
586           end else begin
587             // examples:  uses {b};
588             FromPos:=UnitNameAtom.StartPos;
589             ToPos:=UnitNameAtom.EndPos;
590             if InAtom.StartPos>0 then
591               ToPos:=InAtom.EndPos;
592           end;
593           if not CommentCode(FromPos,ToPos,SourceChangeCache,false) then
594             exit(false);
595         end else begin
596           // replace
597           FromPos:=UnitNameAtom.StartPos;
598           ToPos:=UnitNameAtom.EndPos;
599           if InAtom.StartPos>0 then
600             ToPos:=InAtom.EndPos;
601           if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewText)
602           then exit(false);
603         end;
604       end;
605 
606       if CurPos.Flag=cafComma then begin
607         // read next unit name
608         CommaInFront:=CurPos.StartPos;
609         ReadNextAtom;
610       end else if CurPos.Flag=cafSemicolon then begin
611         break;
612       end else
613         RaiseExceptionFmt(20170421201056,ctsStrExpectedButAtomFound,[';',GetAtom]);
614     until false;
615     Result:=true;
616   end;
617 
618 begin
619   Result:=false;
620   BuildTree(lsrImplementationUsesSectionEnd);
621   SourceChangeCache.MainScanner:=Scanner;
622   ExistingUnits:=nil;
623   try
624     // first collect all units
625     if not FindUsedUnitNames(ExistingUnits) then exit;
626     // then change uses sections
627     Replace(FindMainUsesNode);
628     Replace(FindImplementationUsesNode);
629   finally
630     ExistingUnits.Free;
631   end;
632   Result:=SourceChangeCache.Apply;
633 end;
634 
TStandardCodeTool.AddUnitToUsesSectionnull635 function TStandardCodeTool.AddUnitToUsesSection(UsesNode: TCodeTreeNode;
636   const NewUnitName, NewUnitInFile: string;
637   SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
638 const
639   SpecialUnits: array[1..5] of string = (
640     'cmem',
641     'sharedmem',
642     'lineinfo',
643     'heaptrc',
644     'cthreads'
645     );
646 var
647   Beauty: TBeautifyCodeOptions;
648 
SpecialUnitPrioritynull649   function SpecialUnitPriority(Identifier: PChar): integer;
650   begin
651     Result:=Low(SpecialUnits);
652     while Result<=High(SpecialUnits) do begin
653       if CompareIdentifierPtrs(Pointer(Identifier),Pointer(SpecialUnits[Result]))=0 then
654         exit;
655       inc(Result);
656     end;
657   end;
658 
NextUseUnitNodeInSameBlocknull659   function NextUseUnitNodeInSameBlock(Node: TCodeTreeNode): boolean;
660   var
661     p: LongInt;
662   begin
663     if Node.NextBrother=nil then exit(false);
664     if PositionsInSameLine(Src,Node.EndPos,Node.NextBrother.StartPos) then
665     begin
666       // uses on same line belongs to the same formatting block
667       exit(true);
668     end;
669     // check that there is no comment/directive between
670     p:=FindPrevNonSpace(Src,Node.NextBrother.StartPos-1);
671     if Src[p]<>',' then exit(false);
672     p:=FindPrevNonSpace(Src,p-1);
673     if p>Node.EndPos then exit(false);
674     if LineEndCount(Src,Node.EndPos,Node.NextBrother.StartPos,p)>1 then exit(false);
675     Result:=true;
676   end;
677 
678   procedure AddUseUnit(Lines: TStrings; FirstIndent, Indent: integer;
679     const NewUses: string);
680   var
681     Line: string;
682     l: Integer;
683   begin
684     if Lines.Count=0 then begin
685       Lines.Add(NewUses);
686       exit;
687     end;
688     Line:=Lines[Lines.Count-1];
689     if (atIdentifier in Beauty.DoInsertSpaceAfter)
690     or (atComma in Beauty.DoInsertSpaceInFront) then
691       Line:=Line+' ';
692     Line:=Line+',';
693     l:=length(Line)+length(NewUses)+1; // +1 for the following , or ;
694     if (atComma in Beauty.DoInsertSpaceAfter)
695     or (atIdentifier in Beauty.DoInsertSpaceInFront) then
696       inc(l);
697     if Lines.Count=1 then
698       inc(l,FirstIndent);
699     //DebugLn(['AddUseUnit Lines.Count=',Lines.Count,' l=',l,' Line="',Line,'" NewUses=',NewUses,' FirstIndent=',FirstIndent]);
700     if l<=Beauty.LineLength then begin
701       // append to last line
702       if (atComma in Beauty.DoInsertSpaceAfter)
703       or (atIdentifier in Beauty.DoInsertSpaceInFront) then
704         Line:=Line+' ';
705       Line:=Line+NewUses;
706       Lines[Lines.Count-1]:=Line;
707     end else begin
708       // add new line
709       Lines[Lines.Count-1]:=Line;
710       Line:=Beauty.GetIndentStr(Indent)+NewUses;
711       Lines.Add(Line);
712     end;
713   end;
714 
715 var
716   LineStart, LineEnd, Indent, InsertPos, InsertToPos, InsertLen: integer;
717   NewUsesTerm: string;
718   InsertBehind: Boolean;
719   InsertNode: TCodeTreeNode;
720   Node: TCodeTreeNode;
721   NewCode: TCodeBuffer;
722   DiffPath: String;
723   DiffCnt: Integer;
724   BestDiffCnt: LongInt;
725   AnUnitName: String;
726   AnUnitInFilename: String;
727   i: Integer;
728   NewFilename: String;
729   NewComma: string;
730   Lines: TStringList;
731   FirstIndent: Integer;
732   InsertCode: String;
733   UsesInsertPolicy: TUsesInsertPolicy;
734   Prio: LongInt;
735   FirstNormalUsesNode: TCodeTreeNode;
736   InsertPosFound, CheckSpecialUnits: Boolean;
737 begin
738   Result:=false;
739   if (UsesNode=nil) or (UsesNode.Desc<>ctnUsesSection)
740   or (UsesNode.StartPos<1) or (UsesNode.EndPos<1)
741   or (not IsDottedIdentifier(NewUnitName))
742   then exit;
743   SourceChangeCache.MainScanner:=Scanner;
744   Beauty:=SourceChangeCache.BeautifyCodeOptions;
745 
746   // find nice insert position
747 
748   Prio:=SpecialUnitPriority(PChar(NewUnitName));
749   UsesInsertPolicy:=Beauty.UsesInsertPolicy;
750   if aufLast in Flags then
751     UsesInsertPolicy:=uipLast;
752   InsertPosFound:=false;
753   CheckSpecialUnits:=not (aufNotCheckSpecialUnit in Flags);
754   if CheckSpecialUnits and (Prio<=High(SpecialUnits)) then begin
755     // this is a special unit, insert at the beginning
756     InsertBehind:=false;
757     InsertNode:=UsesNode.FirstChild;
758     while (InsertNode<>nil)
759     and (Prio>SpecialUnitPriority(@Src[InsertNode.StartPos])) do
760       InsertNode:=InsertNode.NextBrother;
761     InsertPosFound:=true;
762     if InsertNode=nil then begin
763       InsertBehind:=true;
764       InsertNode:=UsesNode.LastChild;
765     end;
766   end;
767   if not InsertPosFound then begin
768     FirstNormalUsesNode:=UsesNode.FirstChild;
769     if CheckSpecialUnits and (UsesInsertPolicy<>uipLast) then begin
770       while (FirstNormalUsesNode<>nil)
771       and (SpecialUnitPriority(@Src[FirstNormalUsesNode.StartPos])<Prio) do
772         FirstNormalUsesNode:=FirstNormalUsesNode.NextBrother;
773       if FirstNormalUsesNode=nil then
774         UsesInsertPolicy:=uipLast;
775     end;
776 
777     case UsesInsertPolicy of
778 
779     uipFirst:
780       begin
781         InsertBehind:=false;
782         InsertNode:=FirstNormalUsesNode;
783       end;
784 
785     uipInFrontOfRelated,uipBehindRelated:
786       begin
787         if UsesInsertPolicy=uipBehindRelated then begin
788           InsertNode:=UsesNode.LastChild;
789           InsertBehind:=true;
790         end else begin
791           InsertBehind:=false;
792           InsertNode:=FirstNormalUsesNode;
793         end;
794         NewCode:=FindUnitSource(NewUnitName,'',false);
795         if NewCode<>nil then begin
796           NewFilename:=NewCode.Filename;
797           BestDiffCnt:=High(integer);
798           Node:=FirstNormalUsesNode;
799           while Node<>nil do begin
800             AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
801             // search unit
802             //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Unit=',AnUnitName,' in "',AnUnitInFilename,'"']);
803             NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false);
804             if NewCode<>nil then begin
805               // used unit found -> compute distance
806               DiffPath:=CreateRelativePath(NewCode.Filename,ExtractFilePath(NewFilename));
807               DiffCnt:=0;
808               for i:=1 to length(DiffPath) do
809                 if DiffPath[i]=PathDelim then
810                   inc(DiffCnt);
811               //DebugLn(['TStandardCodeTool.AddUnitToUsesSection DiffCnt=',DiffCnt,' "',NewCode.Filename,'" "',NewFilename,'"']);
812               if UsesInsertPolicy=uipInFrontOfRelated then begin
813                 // insert in front of the first node with the lowest DiffCnt
814                 if BestDiffCnt>DiffCnt then begin
815                   BestDiffCnt:=DiffCnt;
816                   InsertNode:=Node;
817                   InsertBehind:=false;
818                 end;
819               end else begin
820                 // insert behind the last node with the lowest DiffCnt
821                 if BestDiffCnt>=DiffCnt then begin
822                   BestDiffCnt:=DiffCnt;
823                   InsertNode:=Node;
824                   InsertBehind:=true;
825                 end;
826               end;
827             end;
828             Node:=Node.NextBrother;
829           end;
830         end;
831       end;
832 
833     uipLast:
834       begin
835         InsertNode:=UsesNode.LastChild;
836         InsertBehind:=true;
837       end;
838 
839     uipAlphabetically:
840       begin
841         InsertNode:=FirstNormalUsesNode;
842         InsertBehind:=false;
843         while (InsertNode<>nil)
844         and (CompareIdentifiers(PChar(NewUnitName),@Src[InsertNode.StartPos])<0) do
845           InsertNode:=InsertNode.NextBrother;
846         if InsertNode=nil then begin
847           InsertNode:=UsesNode.LastChild;
848           InsertBehind:=true;
849         end;
850       end;
851 
852     end;
853   end;
854 
855   // build insert text  "newunitname in 'file'"
856   NewUsesTerm:=NewUnitName;
857   if NewUnitInFile<>'' then
858     NewUsesTerm:=NewUsesTerm+' '
859       +Beauty.BeautifyKeyWord('in')
860       +' '''+NewUnitInFile+'''';
861 
862   NewComma:=',';
863   if (atComma in Beauty.DoInsertSpaceInFront)
864     or (atIdentifier in Beauty.DoInsertSpaceAfter)
865   then
866     NewComma:=' '+NewComma;
867   if (atComma in Beauty.DoInsertSpaceAfter)
868     or (atIdentifier in Beauty.DoInsertSpaceInFront)
869   then
870     NewComma:=NewComma+' ';
871 
872   if InsertBehind then begin
873     // insert behind unit name, in front of semicolon or comma
874     // for example: uses unit1|, unit2 in 'unit2.pp'|;
875     InsertPos:=InsertNode.EndPos;
876     InsertCode:=NewComma+NewUsesTerm;
877   end else begin
878     // insert in front of unit name, behind 'uses' or comma
879     // for example: uses |unit1, |unit2;
880     InsertPos:=InsertNode.StartPos;
881     InsertCode:=NewUsesTerm+NewComma;
882   end;
883   InsertToPos:=InsertPos;
884 
885   //DebugLn(['TStandardCodeTool.AddUnitToUsesSection InsertNode=',ExtractNode(InsertNode,[]),' InsertBehind=',InsertBehind]);
886 
887   // check if addition fits into the line
888   // if not, rebuild the uses section
889   GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
890   InsertLen:=length(NewUsesTerm)+length(NewComma);
891   //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Line=',copy(Src,LineStart,InsertPos-LineStart),'<InsertPos>',copy(Src,InsertPos,LineEnd-InsertPos),' NewLen=',LineEnd-LineStart+InsertLen,' Max=',Beauty.LineLength,' Addition=',NewUsesTerm]);
892   if (LineEnd-LineStart+InsertLen > Beauty.LineLength) then begin
893     // line too long => reformat block of used units
894     // find start of block of used units
895     Node:=InsertNode;
896     while (Node.PriorBrother<>nil)
897     and NextUseUnitNodeInSameBlock(Node.PriorBrother) do
898       Node:=Node.PriorBrother;
899     InsertPos:=Node.StartPos;
900     GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
901     FirstIndent:=InsertPos-LineStart;
902     Indent:=Beauty.GetLineIndent(Src,InsertPos);
903     if PositionsInSameLine(Src,UsesNode.StartPos,InsertPos) then begin
904       // for example: uses |unit1;
905       inc(Indent,Beauty.Indent);
906     end;
907     // create new block of used units
908     Lines:=TStringList.Create;
909     try
910       while Node<>nil do begin
911         InsertToPos:=Node.EndPos;
912         if (Node=InsertNode) and (not InsertBehind) then
913           AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
914         InsertCode:=ExtractUsedUnitName(Node);
915         if UpAtomIs('IN') then begin
916           ReadNextAtom;
917           InsertCode:=InsertCode+' '+Beauty.BeautifyKeyWord('in')+' '+GetAtom;
918         end;
919         AddUseUnit(Lines,FirstIndent,Indent,InsertCode);
920         if (Node=InsertNode) and InsertBehind then
921           AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
922         if not NextUseUnitNodeInSameBlock(Node) then break;
923         Node:=Node.NextBrother;
924       end;
925       InsertCode:='';
926       for i:=0 to Lines.Count-1 do begin
927         if i>0 then
928           InsertCode:=InsertCode+Beauty.LineEnd;
929         InsertCode:=InsertCode+Lines[i];
930       end;
931     finally
932       Lines.Free;
933     end;
934   end;
935 
936   //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Replace="',copy(Src,InsertPos,InsertToPos-InsertPos),'" with "',InsertCode,'"']);
937   if not SourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertToPos,
938                                    InsertCode) then exit;
939   if not SourceChangeCache.Apply then exit;
940   Result:=true;
941 end;
942 
AddUnitToMainUsesSectionnull943 function TStandardCodeTool.AddUnitToMainUsesSection(const NewUnitName,
944   NewUnitInFile: string; SourceChangeCache: TSourceChangeCache;
945   const Flags: TAddUsesFlags): boolean;
946 begin
947   Result:=AddUnitToSpecificUsesSection(usMain, NewUnitName, NewUnitInFile,
948                                        SourceChangeCache, Flags);
949 end;
950 
AddUnitToImplementationUsesSectionnull951 function TStandardCodeTool.AddUnitToImplementationUsesSection(
952   const NewUnitName, NewUnitInFile: string;
953   SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
954 begin
955   Result:=AddUnitToSpecificUsesSection(usImplementation,
956                           NewUnitName, NewUnitInFile, SourceChangeCache, Flags);
957 end;
958 
TStandardCodeTool.AddUnitToSpecificUsesSectionnull959 function TStandardCodeTool.AddUnitToSpecificUsesSection(
960   UsesSection: TUsesSection; const NewUnitName, NewUnitInFile: string;
961   SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
962 var
963   UsesNode, OtherUsesNode, SectionNode, Node: TCodeTreeNode;
964   NewUsesTerm: string;
965   InsertPos: integer;
966   Junk: TAtomPosition;
967   Beauty: TBeautifyCodeOptions;
968 begin
969   Result:=false;
970   if not IsDottedIdentifier(NewUnitName) then exit;
971   if UsesSection=usMain then begin
972     // quick check using only the main uses section
973     BuildTree(lsrMainUsesSectionEnd);
974     UsesNode:=FindMainUsesNode;
975     if (UsesNode<>nil)
976     and (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk)) then
977       exit(true); // unit already in main uses section
978   end;
979   if GetSourceType=ctnUnit then
980     BuildTree(lsrImplementationUsesSectionEnd)
981   else if UsesSection=usImplementation then begin
982     MoveCursorToNodeStart(Tree.Root);
983     RaiseException(20170421201102,
984       ctsCanNotAddAUnitToTheImplementationBecauseOnlyAUnitH);
985   end;
986   SourceChangeCache.MainScanner:=Scanner;
987   Beauty:=SourceChangeCache.BeautifyCodeOptions;
988   SourceChangeCache.BeginUpdate;
989   try
990     UsesNode:=FindMainUsesNode;
991     OtherUsesNode:=FindImplementationUsesNode;
992     if UsesSection=usImplementation then begin
993       SectionNode:=UsesNode;
994       UsesNode:=OtherUsesNode;
995       OtherUsesNode:=SectionNode;
996     end;
997     // remove unit from other uses section
998     if (OtherUsesNode<>nil) then
999       RemoveUnitFromUsesSection(OtherUsesNode,NewUnitName,SourceChangeCache);
1000 
1001     if UsesNode<>nil then begin
1002       // add unit to existing uses section
1003       if not (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk))
1004       then begin
1005         if not AddUnitToUsesSection(UsesNode,NewUnitName,NewUnitInFile,
1006                                     SourceChangeCache,Flags)
1007         then
1008           exit;
1009       end;
1010     end else begin
1011       // create a new uses section
1012       if Tree.Root=nil then exit;
1013       SectionNode:=Tree.Root;
1014       InsertPos:=0;
1015       NewUsesTerm:='';
1016       if SectionNode.Desc=ctnUnit then begin
1017         // unit
1018         case UsesSection of
1019         usMain: SectionNode:=FindInterfaceNode;
1020         usImplementation: SectionNode:=FindImplementationNode;
1021         end;
1022         if SectionNode<>nil then begin
1023           // add uses to existing interface/implementation before any content
1024           MoveCursorToNodeStart(SectionNode);
1025           ReadNextAtom;
1026           InsertPos := CurPos.EndPos;
1027         end else begin
1028           // section is missing => add it
1029           SectionNode:=Tree.Root;
1030           case UsesSection of
1031           usMain: NewUsesTerm:='interface';
1032           usImplementation: NewUsesTerm:='implementation';
1033           end;
1034           NewUsesTerm:=Beauty.BeautifyKeyWord(NewUsesTerm)
1035                       +Beauty.LineEnd;
1036           if SectionNode.FirstChild<>nil then begin
1037             // unit not empty => add in front of first node
1038             InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.FirstChild.StartPos,
1039               true);
1040           end else begin
1041             // unit empty => add at end
1042             InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
1043           end;
1044         end;
1045       end;
1046       if InsertPos<1 then begin
1047         // not a unit (i.e. program)
1048         // => insert after title and directives
1049         Node:=SectionNode.Next;
1050         if (Node<>nil) and (Node.Desc=ctnSrcName) then
1051           Node:=Node.NextSkipChilds;
1052         if Node<>nil then begin
1053           InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos,
1054             true);
1055         end else begin
1056           // program empty => add at end
1057           InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
1058         end;
1059       end;
1060       NewUsesTerm:=NewUsesTerm+Beauty.BeautifyKeyWord('uses')+' '+NewUnitName;
1061       if NewUnitInFile<>'' then
1062         NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+''';'
1063       else
1064         NewUsesTerm:=NewUsesTerm+';';
1065       if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
1066         NewUsesTerm) then exit;
1067     end;
1068     Result:=true;
1069   finally
1070     if not Result then
1071       SourceChangeCache.Clear;
1072     if not SourceChangeCache.EndUpdate then
1073       Result:=false;
1074   end;
1075 end;
1076 
TStandardCodeTool.UnitExistsInUsesSectionnull1077 function TStandardCodeTool.UnitExistsInUsesSection(UsesSection: TUsesSection;
1078   const AnUnitName: string): boolean;
1079 var
1080   UsesNode: TCodeTreeNode;
1081 begin
1082   Result:=false;
1083   if not IsDottedIdentifier(AnUnitName) then
1084     exit;
1085   if UsesSection=usMain then
1086     BuildTree(lsrMainUsesSectionEnd)
1087   else
1088     BuildTree(lsrImplementationUsesSectionEnd);
1089   case UsesSection Of
1090     usMain: UsesNode:=FindMainUsesNode;
1091     usImplementation: UsesNode:=FindImplementationUsesNode;
1092   end;
1093   Result:=UnitExistsInUsesSection(UsesNode,AnUnitName);
1094 end;
1095 
TStandardCodeTool.UnitExistsInUsesSectionnull1096 function TStandardCodeTool.UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
1097   const AnUnitName: string): boolean;
1098 begin
1099   Result:=false;
1100   if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
1101     exit;
1102   MoveCursorToNodeStart(UsesNode);
1103   ReadNextAtom; // read 'uses'
1104   repeat
1105     ReadNextAtom; // read name
1106     if not AtomIsIdentifier then exit;
1107     if ReadAndCompareUsedUnit(AnUnitName) then begin
1108       // unit found
1109       exit(true);
1110     end;
1111     if UpAtomIs('IN') then begin
1112       ReadNextAtom;
1113       ReadNextAtom;
1114     end;
1115     if CurPos.Flag=cafSemicolon then break;
1116     if CurPos.Flag<>cafComma then break;
1117   until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
1118 end;
1119 
RemoveUnitFromUsesSectionnull1120 function TStandardCodeTool.RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
1121   const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;
1122 var UnitPos, StartPos, EndPos: integer;
1123   Found: Boolean;
1124 begin
1125   Result:=false;
1126   if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
1127     exit;
1128   MoveCursorToNodeStart(UsesNode);
1129   ReadNextAtom; // read 'uses'
1130   UnitPos:=0;
1131   repeat
1132     EndPos:=CurPos.StartPos;
1133     ReadNextAtom; // read name
1134     if not AtomIsIdentifier then exit;
1135     inc(UnitPos);
1136     StartPos:=CurPos.StartPos;
1137     Found:=ReadAndCompareUsedUnit(AnUnitName);
1138     if UpAtomIs('IN') then begin
1139       ReadNextAtom;
1140       ReadNextAtom;
1141     end;
1142     if Found then begin
1143       // unit found
1144       SourceChangeCache.MainScanner:=Scanner;
1145       if UnitPos=1 then begin
1146         // first unit in uses section
1147         if AtomIsChar(';') then begin
1148           // last unit in uses section -> delete whole uses section
1149           StartPos:=FindLineEndOrCodeInFrontOfPosition(UsesNode.StartPos,true,true);
1150           EndPos:=UsesNode.EndPos;
1151         end else begin
1152           // not last unit -> delete with comma behind
1153           EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
1154           if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
1155             StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
1156         end;
1157       end else begin
1158         // not first unit in uses section
1159         if AtomIsChar(';') then begin
1160           // last unit -> delete with comma in front
1161           StartPos:=EndPos;
1162           EndPos:=CurPos.StartPos;
1163         end else if PositionsInSameLine(Src,EndPos,StartPos) then begin
1164           // not first unit in line -> delete with comma in front
1165           StartPos:=EndPos;
1166           EndPos:=CurPos.StartPos;
1167         end else begin
1168           // first unit in line -> delete with comma behind
1169           EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
1170           if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
1171             StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
1172         end;
1173       end;
1174       if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
1175         exit;
1176       if not SourceChangeCache.Apply then exit;
1177       Result:=true;
1178       exit;
1179     end;
1180     if AtomIsChar(';') then break;
1181     if not AtomIsChar(',') then break;
1182   until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
1183   Result:=true;
1184 end;
1185 
RemoveUnitFromAllUsesSectionsnull1186 function TStandardCodeTool.RemoveUnitFromAllUsesSections(
1187   const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;
1188 
RemoveFromSectionnull1189   function RemoveFromSection(UsesNode: TCodeTreeNode): boolean;
1190   begin
1191     Result:=(UsesNode=nil)
1192       or (RemoveUnitFromUsesSection(UsesNode,AnUnitName,SourceChangeCache));
1193   end;
1194 
1195 begin
1196   Result:=false;
1197   if (AnUnitName='') or (SourceChangeCache=nil) then exit;
1198   BuildTree(lsrImplementationUsesSectionEnd);
1199 
1200   SourceChangeCache.BeginUpdate;
1201   try
1202     if not RemoveFromSection(FindMainUsesNode) then exit;
1203     if not RemoveFromSection(FindImplementationUsesNode) then exit;
1204   finally
1205     Result:=SourceChangeCache.EndUpdate;
1206   end;
1207 end;
1208 
TStandardCodeTool.FixUsedUnitCasenull1209 function TStandardCodeTool.FixUsedUnitCase(
1210   SourceChangeCache: TSourceChangeCache): boolean;
1211 
FixUsesSectionnull1212   function FixUsesSection(UsesNode: TCodeTreeNode): boolean;
1213   begin
1214     Result:=(UsesNode=nil) or FixUsedUnitCaseInUsesSection(UsesNode,SourceChangeCache);
1215   end;
1216 
1217 begin
1218   //debugln('TStandardCodeTool.FixUsedUnitCase ',MainFilename);
1219   Result:=false;
1220   BuildTree(lsrImplementationUsesSectionEnd);
1221   if not FixUsesSection(FindMainUsesNode) then exit;
1222   if not FixUsesSection(FindImplementationUsesNode) then exit;
1223   Result:=true;
1224 end;
1225 
FixUsedUnitCaseInUsesSectionnull1226 function TStandardCodeTool.FixUsedUnitCaseInUsesSection(
1227   UsesNode: TCodeTreeNode; SourceChangeCache: TSourceChangeCache): boolean;
1228 
FindUnitnull1229   function FindUnit(AFilename: string): string;
1230   var
1231     CurDir: String;
1232     MakeRelative: Boolean;
1233   begin
1234     Result:='';
1235     AFilename:=TrimFilename(AFilename);
1236     CurDir:='';
1237     if FilenameIsAbsolute(AFilename) then begin
1238       MakeRelative:=false;
1239     end else begin
1240       MakeRelative:=true;
1241       if TCodeBuffer(Scanner.MainCode).IsVirtual then exit;
1242       CurDir:=ExtractFilePath(TCodeBuffer(Scanner.MainCode).Filename);
1243       AFilename:=CurDir+AFilename;
1244     end;
1245     Result:=DirectoryCache.Pool.FindDiskFilename(AFilename,true);
1246     if Result='' then exit;
1247     if MakeRelative then
1248       Result:=CreateRelativePath(Result,CurDir);
1249   end;
1250 
1251 var
1252   UnitInFilename: String;
1253   Changed: Boolean;
1254   RealUnitInFilename: String;
1255   UnitNameRange: TAtomPosition;
1256   InAtom: TAtomPosition;
1257 begin
1258   Result:=false;
1259   if (UsesNode=nil) then exit;
1260   MoveCursorToNodeStart(UsesNode);
1261   ReadNextAtom; // read 'uses'
1262   Changed:=false;
1263   repeat
1264     ReadNextAtom; // read name
1265     if not ReadNextUsedUnit(UnitNameRange,InAtom,false) then exit;
1266     if InAtom.StartPos>1 then begin
1267       UnitInFilename:=GetAtom(InAtom);
1268       //debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection A UnitInFilename="',UnitInFilename,'"');
1269       if (UnitInFilename<>'') and (UnitInFilename[1]='''') then begin
1270         UnitInFilename:=copy(UnitInFilename,2,length(UnitInFilename)-2);
1271         RealUnitInFilename:=FindUnit(UnitInFilename);
1272         //debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection B RealUnitInFilename="',RealUnitInFilename,'"');
1273         if (RealUnitInFilename<>'')
1274         and (RealUnitInFilename<>UnitInFilename) then begin
1275           if not Changed then begin
1276             SourceChangeCache.MainScanner:=Scanner;
1277             Changed:=true;
1278           end;
1279           debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection Replacing UnitInFilename="',UnitInFilename,'" with "',RealUnitInFilename,'"');
1280           if not SourceChangeCache.Replace(gtNone,gtNone,
1281             InAtom.StartPos,InAtom.EndPos,''''+RealUnitInFilename+'''') then exit;
1282         end;
1283       end;
1284     end;
1285     if CurPos.Flag=cafSemicolon then break;
1286     if CurPos.Flag<>cafComma then exit;
1287   until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
1288   if Changed and (not SourceChangeCache.Apply) then exit;
1289   Result:=true;
1290 end;
1291 
FindUsedUnitNamesnull1292 function TStandardCodeTool.FindUsedUnitNames(var MainUsesSection,
1293   ImplementationUsesSection: TStrings): boolean;
1294 var
1295   MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
1296 begin
1297   MainUsesSection:=nil;
1298   ImplementationUsesSection:=nil;
1299   // find the uses sections
1300   BuildTree(lsrImplementationUsesSectionEnd);
1301   MainUsesNode:=FindMainUsesNode;
1302   ImplementatioUsesNode:=FindImplementationUsesNode;
1303   // create lists
1304   try
1305     MainUsesSection:=UsesSectionToUnitNames(MainUsesNode);
1306     ImplementationUsesSection:=UsesSectionToUnitNames(ImplementatioUsesNode);
1307   except
1308     FreeAndNil(MainUsesSection);
1309     FreeAndNil(ImplementationUsesSection);
1310     raise;
1311   end;
1312   Result:=true;
1313 end;
1314 
FindUsedUnitNamesnull1315 function TStandardCodeTool.FindUsedUnitNames(var List: TStringToStringTree
1316   ): boolean;
1317 
1318   procedure Collect(UsesNode: TCodeTreeNode; const Tag: string);
1319   var
1320     UnitNameAtom: TAtomPosition;
1321     InAtom: TAtomPosition;
1322     OldTag: string;
1323     AnUnitName: String;
1324   begin
1325     if UsesNode=nil then exit;
1326     MoveCursorToUsesStart(UsesNode);
1327     repeat
1328       // read next unit name
1329       ReadNextUsedUnit(UnitNameAtom, InAtom);
1330       AnUnitName:=GetAtom(UnitNameAtom);
1331       // tag unit in list
1332       OldTag:=List[AnUnitName];
1333       if System.Pos(Tag,OldTag)<1 then
1334         List[AnUnitName]:=OldTag+Tag;
1335       if CurPos.Flag=cafComma then begin
1336         // read next unit name
1337         ReadNextAtom;
1338       end else if CurPos.Flag=cafSemicolon then begin
1339         break;
1340       end else
1341         RaiseExceptionFmt(20170421201120,ctsStrExpectedButAtomFound,[';',GetAtom]);
1342     until false;
1343     Result:=true;
1344   end;
1345 
1346 begin
1347   // find the uses sections
1348   List:=TStringToStringTree.Create(false);
1349   BuildTree(lsrImplementationUsesSectionEnd);
1350   Collect(FindMainUsesNode,'Main');
1351   Collect(FindImplementationUsesNode,'Implementation');
1352   Result:=true;
1353 end;
1354 
FindUsedUnitFilesnull1355 function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection: TStrings
1356   ): boolean;
1357 var
1358   MainUsesNode: TCodeTreeNode;
1359 begin
1360   MainUsesSection:=nil;
1361   // find the uses sections
1362   BuildTree(lsrMainUsesSectionEnd);
1363   MainUsesNode:=FindMainUsesNode;
1364   // create lists
1365   try
1366     MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
1367   except
1368     FreeAndNil(MainUsesSection);
1369     raise;
1370   end;
1371   Result:=true;
1372 end;
1373 
FindUsedUnitFilesnull1374 function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection,
1375   ImplementationUsesSection: TStrings): boolean;
1376 var
1377   MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
1378 begin
1379   MainUsesSection:=nil;
1380   ImplementationUsesSection:=nil;
1381   // find the uses sections
1382   BuildTree(lsrImplementationUsesSectionEnd);
1383   MainUsesNode:=FindMainUsesNode;
1384   ImplementatioUsesNode:=FindImplementationUsesNode;
1385   // create lists
1386   try
1387     MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
1388     ImplementationUsesSection:=UsesSectionToFilenames(ImplementatioUsesNode);
1389   except
1390     FreeAndNil(MainUsesSection);
1391     FreeAndNil(ImplementationUsesSection);
1392     raise;
1393   end;
1394   Result:=true;
1395 end;
1396 
1397 {------------------------------------------------------------------------------
1398   function TStandardCodeTool.FindDelphiProjectUnits(var FoundInUnits,
1399     MissingInUnits, NormalUnits: TStrings): boolean;
1400 
1401   Reads the main uses section and tries to find each unit file having
1402   an 'in' modifier.
1403   The associated objects in the list will be the found codebuffers.
1404   FoundInUnits returns the list of found 'in' unitnames plus TCodeBuffer
1405   MissingInUnits returns the list of missing 'in' unitnames
1406   NormalUnits returns the list of unitnames plus TCodeBuffer (if found)
1407 
1408   If no codebuffer was found/created then the filename will be the unit name
1409   plus the 'in' extension.
1410 ------------------------------------------------------------------------------}
FindDelphiProjectUnitsnull1411 function TStandardCodeTool.FindDelphiProjectUnits(out FoundInUnits,
1412   MissingInUnits, NormalUnits: TStrings; UseContainsSection: boolean;
1413   IgnoreNormalUnits: boolean): boolean;
1414 var
1415   AnUnitName, AnUnitInFilename: string;
1416   NewCode: TCodeBuffer;
1417   UsesNode: TCodeTreeNode;
1418   Node: TCodeTreeNode;
1419 begin
1420   Result:=false;
1421   FoundInUnits:=nil;
1422   MissingInUnits:=nil;
1423   NormalUnits:=nil;
1424   // find the uses sections
1425   BuildTree(lsrMainUsesSectionEnd);
1426   UsesNode:=FindMainUsesNode(UseContainsSection);
1427   if UsesNode=nil then exit;
1428   FoundInUnits:=TStringListUTF8Fast.Create;
1429   MissingInUnits:=TStringListUTF8Fast.Create;
1430   if IgnoreNormalUnits then
1431     NormalUnits:=nil
1432   else
1433     NormalUnits:=TStringListUTF8Fast.Create;
1434   Node:=UsesNode.FirstChild;
1435   while Node<>nil do begin
1436     // read next unit name
1437     AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
1438     // find unit file
1439     if AnUnitInFilename<>'' then begin
1440       // An 'in' unit => Delphi project file
1441       NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
1442       if (NewCode=nil) then begin
1443         // no source found
1444         MissingInUnits.Add(AnUnitName+' in '+AnUnitInFilename);
1445       end else begin
1446         // source found => add filename to list
1447         FoundInUnits.AddObject(AnUnitName+' in '+AnUnitInFilename,NewCode);
1448       end;
1449     end else if AnUnitName<>'' then begin
1450       // the units without 'in' are 'Forms' or units added by the user
1451       if not IgnoreNormalUnits then begin
1452         NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
1453         NormalUnits.AddObject(AnUnitName,NewCode);
1454       end;
1455     end;
1456     Node:=Node.NextBrother;
1457   end;
1458   Result:=true;
1459 end;
1460 
1461 {------------------------------------------------------------------------------
1462   function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
1463     ): TStrings;
1464 
1465   Reads the uses section backwards and tries to find each unit file.
1466   The associated objects in the list will be the found codebuffers.
1467   If no codebuffer was found/created then the filename will be the unit name
1468   plus the 'in' extension.
1469 ------------------------------------------------------------------------------}
TStandardCodeTool.UsesSectionToFilenamesnull1470 function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
1471   ): TStrings;
1472 var
1473   AnUnitName, AnUnitInFilename: string;
1474   NewCode: TCodeBuffer;
1475   UnitFilename: string;
1476   Node: TCodeTreeNode;
1477 begin
1478   Result:=TStringList.Create;
1479   if UsesNode=nil then exit;
1480   Node:=UsesNode.LastChild;
1481   while Node<>nil do begin
1482     // read unit name
1483     AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
1484     if AnUnitName<>'' then begin
1485       // find unit file
1486       NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
1487       if (NewCode=nil) then begin
1488         // no source found
1489         UnitFilename:=AnUnitName;
1490         if AnUnitInFilename<>'' then
1491           UnitFilename:=UnitFilename+' in '+AnUnitInFilename;
1492       end else begin
1493         // source found
1494         UnitFilename:=NewCode.Filename;
1495       end;
1496       // add filename to list
1497       Result.AddObject(UnitFilename,NewCode);
1498     end;
1499     Node:=Node.PriorBrother;
1500   end;
1501 end;
1502 
TStandardCodeTool.UsesSectionToUnitnamesnull1503 function TStandardCodeTool.UsesSectionToUnitnames(UsesNode: TCodeTreeNode
1504   ): TStrings;
1505 var
1506   AnUnitName: string;
1507   Node: TCodeTreeNode;
1508 begin
1509   Result:=TStringList.Create;
1510   if UsesNode=nil then exit;
1511   Node:=UsesNode.LastChild;
1512   while Node<>nil do begin
1513     // read unit name
1514     AnUnitName:=ExtractUsedUnitName(Node);
1515     if AnUnitName<>'' then
1516       Result.Add(AnUnitName);
1517     Node:=Node.PriorBrother;
1518   end;
1519 end;
1520 
TStandardCodeTool.FindMissingUnitsnull1521 function TStandardCodeTool.FindMissingUnits(var MissingUnits: TStrings;
1522   FixCase: boolean; SearchImplementation: boolean;
1523   SourceChangeCache: TSourceChangeCache): boolean;
1524 const
1525   FPCSrcSearchRequiresPPU = true;
1526 
CheckUsesSectionnull1527   function CheckUsesSection(UsesNode: TCodeTreeNode): boolean;
1528   var
1529     OldUnitName: String;
1530     OldInFilename: String;
1531     AFilename: String;
1532     s: String;
1533     NewUnitName: String;
1534     NewInFilename: String;
1535     FromPos: LongInt;
1536     ToPos: LongInt;
1537     Node: TCodeTreeNode;
1538   begin
1539     if UsesNode=nil then exit(true);
1540 
1541     Node:=UsesNode.FirstChild;
1542     while Node<>nil do begin
1543       // read next unit name
1544       OldUnitName:=ExtractUsedUnitName(Node,@OldInFilename);
1545       // find unit file
1546       NewUnitName:=OldUnitName;
1547       NewInFilename:=OldInFilename;
1548       //debugln(['CheckUsesSection NewUnitName="',NewUnitName,'" NewInFilename="',NewInFilename,'"']);
1549       AFilename:=DirectoryCache.FindUnitSourceInCompletePath(
1550          NewUnitName,NewInFilename,true,FPCSrcSearchRequiresPPU,AddedNameSpace);
1551       s:=NewUnitName;
1552       if NewInFilename<>'' then
1553         s:=s+' in '''+NewInFilename+'''';
1554       if AFilename<>'' then begin
1555         // unit found
1556         if FixCase
1557         and ((NewUnitName<>OldUnitName) or (NewInFilename<>OldInFilename)) then
1558         begin
1559           // fix case
1560           FromPos:=Node.StartPos;
1561           ToPos:=Node.EndPos;
1562           SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,s);
1563           DebugLn('TStandardCodeTool.FindMissingUnits.CheckUsesSection fix case Unit Name(',OldUnitName,'->',NewUnitName,') InFile(',OldInFilename,'->',NewInFilename,')');
1564         end;
1565       end else begin
1566         // unit not found
1567         if MissingUnits=nil then MissingUnits:=TStringList.Create;
1568         MissingUnits.Add(s);
1569       end;
1570       Node:=Node.NextBrother;
1571     end;
1572     Result:=true;
1573   end;
1574 
1575 begin
1576   Result:=false;
1577   BuildTree(lsrImplementationUsesSectionEnd);
1578   if FixCase then
1579     SourceChangeCache.MainScanner:=Scanner;
1580   try
1581     if not CheckUsesSection(FindMainUsesNode) then exit;
1582     if SearchImplementation
1583     and not CheckUsesSection(FindImplementationUsesNode) then exit;
1584   except
1585     FreeAndNil(MissingUnits);
1586     raise;
1587   end;
1588   if FixCase then
1589     Result:=SourceChangeCache.Apply
1590   else
1591     Result:=true;
1592 end;
1593 
CommentUnitsInUsesSectionnull1594 function TStandardCodeTool.CommentUnitsInUsesSection(MissingUnits: TStrings;
1595   SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
1596 // Examples:
1597 // 1. uses {a,} b, c;    commenting one unit not at end
1598 // 2. uses a, {b,} c;    commenting one unit not at end
1599 // 3. uses {a, b,} c;    commenting several units not at end
1600 // 4. uses a{, b, c} ;   commenting units at end
1601 // 5. {uses a, b, c;}    commenting all units
1602 // 6. uses {a,} b{, c};  commenting several units
1603 
1604   procedure Comment(StartPos, EndPos: integer);
1605   begin
1606     //debugln(['Comment ',dbgstr(copy(Src,StartPos,EndPos-StartPos))]);
1607     CommentCode(StartPos,EndPos,SourceChangeCache,false);
1608   end;
1609 
1610 var
1611   i: Integer;
1612   CurUnitName: String;
1613   CommentCurUnit: Boolean;
1614   FirstCommentUnitStart: Integer;
1615   LastCommaAfterCommentUnitsStart: Integer;
1616   LastNormalUnitEnd: Integer;
1617   LastCommentUnitEnd: Integer;
1618   Node: TCodeTreeNode;
1619 begin
1620   Result:=true;
1621   if UsesNode=nil then exit;
1622   FirstCommentUnitStart:=-1;
1623   LastCommaAfterCommentUnitsStart:=-1;
1624   LastNormalUnitEnd:=-1;
1625   LastCommentUnitEnd:=-1;
1626   Node:=UsesNode.FirstChild;
1627   while Node<>nil do begin
1628     // check if unit should be commented
1629     CurUnitName:=ExtractUsedUnitName(Node);
1630     // Note: CurPos is now on atom behind used unit, i.e. comma or semicolon
1631     i:=MissingUnits.Count-1;
1632     while (i>=0)
1633     and (CompareIdentifiers(PChar(Pointer(MissingUnits[i])),
1634                             PChar(Pointer(CurUnitName)))<>0) do
1635       dec(i);
1636     CommentCurUnit:=i>=0;
1637     //debugln('CommentUnitsInUsesSection CurUnitName="',CurUnitName,'" CommentCurUnit=',dbgs(CommentCurUnit));
1638 
1639     if CommentCurUnit then begin
1640       // unit should be commented
1641       if FirstCommentUnitStart<1 then FirstCommentUnitStart:=Node.StartPos;
1642       LastCommentUnitEnd:=Node.EndPos;
1643     end else begin
1644       // unit should be kept
1645       LastNormalUnitEnd:=Node.EndPos;
1646       if FirstCommentUnitStart>=1 then begin
1647         // there are some units to be commented
1648         // See examples: 1., 2., 3. and 6.
1649         Comment(FirstCommentUnitStart,LastCommaAfterCommentUnitsStart);
1650         FirstCommentUnitStart:=-1;
1651         LastCommentUnitEnd:=-1;
1652         LastCommaAfterCommentUnitsStart:=-1;
1653       end;
1654     end;
1655 
1656     if CommentCurUnit then
1657       LastCommaAfterCommentUnitsStart:=CurPos.EndPos;
1658 
1659     if CurPos.Flag<>cafComma then begin
1660       if CommentCurUnit then begin
1661         // last unit must be commented
1662         if LastNormalUnitEnd>=1 then begin
1663           // comment last unit and keep some units in front
1664           // See example: 4.
1665           Comment(LastNormalUnitEnd,LastCommentUnitEnd);
1666         end else begin
1667           // all units should be commented
1668           // See example: 5.
1669           Comment(UsesNode.StartPos,CurPos.EndPos);
1670         end;
1671       end;
1672       break;
1673     end;
1674 
1675     Node:=Node.NextBrother;
1676   end;
1677 end;
1678 
CommentUnitsInUsesSectionsnull1679 function TStandardCodeTool.CommentUnitsInUsesSections(MissingUnits: TStrings;
1680   SourceChangeCache: TSourceChangeCache): boolean;
1681 begin
1682   if (MissingUnits=nil) or (MissingUnits.Count=0) then
1683     exit(true);
1684   Result:=false;
1685   BuildTree(lsrInitializationStart);
1686   SourceChangeCache.MainScanner:=Scanner;
1687   if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindMainUsesNode) then exit;
1688   if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindImplementationUsesNode) then exit;
1689   if not SourceChangeCache.Apply then exit;
1690   Result:=true;
1691 end;
1692 
FindUnusedUnitsnull1693 function TStandardCodeTool.FindUnusedUnits(Units: TStrings): boolean;
1694 // returns a list of unitname=flags
1695 // flags are a comma separated list of words:
1696 //   'implementation': unit is in implementation uses section
1697 //   'used': an identifier of the interface is used
1698 //   'code': unit has non empty initialization/finalization section
1699 var
1700   Identifiers: TAVLTree;// all identifiers used in this unit
1701 
IsUnitAlreadyCheckednull1702   function IsUnitAlreadyChecked(const AnUnitName: string): boolean;
1703   var
1704     i: Integer;
1705   begin
1706     for i:=0 to Units.Count-1 do
1707       if SysUtils.CompareText(Units.Names[i],AnUnitName)=0 then exit(true);
1708     Result:=false;
1709   end;
1710 
1711   procedure GatherIdentifiersInRange(StartPos, EndPos: integer);
1712   // gather all used identifiers from this unit in the range Startpos..EndPos
1713   var
1714     Identifier: PChar;
1715   begin
1716     if (StartPos<1) or (StartPos>=EndPos) then exit;
1717     MoveCursorToCleanPos(StartPos);
1718     repeat
1719       ReadNextAtom;
1720       if CurPos.StartPos>=EndPos then break;
1721       if IsIdentStartChar[Src[CurPos.StartPos]] then begin
1722         Identifier:=@Src[CurPos.StartPos];
1723         if Identifiers.Find(Identifier)=nil then begin
1724           {$IFDEF VerboseFindUnusedUnits}
1725           DebugLn(['Used Identifier=',GetIdentifier(Identifier)]);
1726           {$ENDIF}
1727           Identifiers.Add(Identifier);
1728         end;
1729       end;
1730     until false;
1731   end;
1732 
1733   procedure GatherIdentifiers;
1734   // gather all used identifiers from this unit
1735   var
1736     StartPos: Integer;
1737 
1738     procedure Gather(EndPos: integer);
1739     begin
1740       if StartPos<1 then exit;
1741       GatherIdentifiersInRange(StartPos,EndPos);
1742       StartPos:=-1;
1743     end;
1744 
1745   var
1746     Node: TCodeTreeNode;
1747   begin
1748     if Identifiers<>nil then exit;
1749     Identifiers:=TAVLTree.Create(@CompareIdentifierPtrs);
1750     {$IFDEF VerboseFindUnusedUnits}
1751     DebugLn(['GatherIdentifiers ']);
1752     {$ENDIF}
1753     StartPos:=-1;
1754     Node:=Tree.Root;
1755     while Node<>nil do begin
1756       case Node.Desc of
1757       ctnUseUnit,ctnUsesSection,ctnUseUnitNamespace,ctnUseUnitClearName,
1758       ctnProgram,ctnUnit,ctnPackage,ctnLibrary,ctnEndPoint:
1759         begin
1760           // skip node
1761           Gather(Node.StartPos);
1762         end;
1763       ctnEnumIdentifier,
1764       ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition:
1765         begin
1766           // start reading behind identifier
1767           Gather(Node.StartPos);
1768           MoveCursorToCleanPos(Node.StartPos);
1769           ReadNextAtom;
1770           StartPos:=CurPos.EndPos;
1771         end;
1772       else
1773         if StartPos<1 then
1774           StartPos:=Node.StartPos;
1775       end;
1776       Node:=Node.Next;
1777     end;
1778   end;
1779 
InterfaceIsUsednull1780   function InterfaceIsUsed(Tool: TFindDeclarationTool;
1781     IntfNode: TCodeTreeNode): boolean;
1782 
IsIdentifierUsednull1783     function IsIdentifierUsed(StartPos: integer): boolean;
1784     begin
1785       {$IFDEF VerboseFindUnusedUnits}
1786       if CompareIdentifiers(PChar(GetIdentifier(@Tool.Src[StartPos])),'TComponent')=0 then
1787         DebugLn(['IsIdentifierUsed ',GetIdentifier(@Tool.Src[StartPos])]);
1788       {$ENDIF}
1789       Result:=Identifiers.Find(@Tool.Src[StartPos])<>nil;
1790     end;
1791 
IsNodeVisiblenull1792     function IsNodeVisible(Node: TCodeTreeNode): boolean;
1793     begin
1794       Result:=false;
1795       Node:=Node.Parent;
1796       while Node<>nil do begin
1797         if Node.Desc=ctnInterface then exit(true);
1798         if Node.Desc in AllClasses then exit;
1799         Node:=Node.Parent;
1800       end;
1801     end;
1802 
FindProceduresInHelpernull1803     function FindProceduresInHelper(Node: TCodeTreeNode): boolean;
1804     begin
1805       Result := False;
1806       Node := Node.FirstChild;
1807       while (Node<>nil) and (Node.Desc <> ctnEndPoint) do
1808       begin
1809         if (Node.Desc = ctnProcedure) and (Node.FirstChild<>nil) then
1810         begin
1811           if IsIdentifierUsed(Node.FirstChild.StartPos) then
1812             Exit(True);
1813         end;
1814         Node:=Node.Next;
1815       end;
1816     end;
1817 
1818   var
1819     Node: TCodeTreeNode;
1820   begin
1821     Result:=true;
1822     Node:=IntfNode.FirstChild;
1823     while Node<>nil do begin
1824       case Node.Desc of
1825       ctnTypeHelper, ctnRecordHelper, ctnClassHelper:
1826         if FindProceduresInHelper(Node) then exit;
1827       ctnEnumIdentifier:
1828         if IsIdentifierUsed(Node.StartPos) then exit;
1829       ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnGenericName:
1830         if IsNodeVisible(Node) and IsIdentifierUsed(Node.StartPos) then exit;
1831       ctnProcedure:
1832         if (Node.Parent.Desc=ctnInterface)
1833         and (Node.FirstChild<>nil)
1834         and (Node.FirstChild.Desc=ctnProcedureHead)
1835         and IsIdentifierUsed(Node.FirstChild.StartPos) then exit;
1836       ctnGlobalProperty:
1837         if Tool.MoveCursorToPropName(Node)
1838         and IsIdentifierUsed(Tool.CurPos.StartPos) then exit;
1839       end;
1840       Node:=Node.Next;
1841     end;
1842     Result:=false;
1843   end;
1844 
1845   procedure CheckUnit(Tool: TFindDeclarationTool;
1846     out HasCode, UseInterface: boolean);
1847   var
1848     Node: TCodeTreeNode;
1849     Identifier: String;
1850   begin
1851     GatherIdentifiers;
1852     HasCode:=false;
1853     UseInterface:=false;
1854     // parse used unit
1855     Tool.BuildTree(lsrEnd);
1856     Node:=Tool.Tree.Root;
1857     while (Node<>nil) do begin
1858       case Node.Desc of
1859       ctnUnit,ctnPackage,ctnLibrary:
1860         begin
1861           Identifier:=Tool.ExtractSourceName;
1862           if Identifiers.Find(PChar(Identifier))<>nil then
1863             UseInterface:=true;
1864         end;
1865       ctnInterface:
1866         if not UseInterface then
1867           UseInterface:=InterfaceIsUsed(Tool,Node);
1868       ctnInitialization,ctnFinalization,ctnBeginBlock:
1869         begin
1870           HasCode:=true;
1871           break;
1872         end;
1873       end;
1874       Node:=Node.NextBrother;
1875     end;
1876   end;
1877 
1878   procedure CheckUsesSection(UsesNode: TCodeTreeNode; InImplementation: boolean);
1879   var
1880     Unit_Name: String;
1881     UnitInFilename: String;
1882     Tool: TFindDeclarationTool;
1883     HasCode: boolean;
1884     UseInterface: boolean;
1885     Flags: String;
1886     Node: TCodeTreeNode;
1887   begin
1888     HasCode:=false;
1889     UseInterface:=false;
1890     if UsesNode=nil then exit;
1891     Node:=UsesNode.FirstChild;
1892     while Node<>nil do begin
1893       Unit_Name:=ExtractUsedUnitName(Node,@UnitInFilename);
1894       if not IsUnitAlreadyChecked(Unit_Name) then begin
1895         // try to load the used unit
1896         {$IFDEF VerboseFindUnusedUnits}
1897         DebugLn(['CheckUsesSection ',Unit_Name,' in ',UnitInFilename]);
1898         {$ENDIF}
1899         Tool:=FindCodeToolForUsedUnit(Unit_Name,UnitInFilename,true);
1900         // parse the used unit
1901         CheckUnit(Tool,HasCode,UseInterface);
1902         Flags:='';
1903         if InImplementation then
1904           Flags:=Flags+',implementation';
1905         if HasCode then
1906           Flags:=Flags+',code';
1907         if UseInterface then
1908           Flags:=Flags+',used';
1909         {$IFDEF VerboseFindUnusedUnits}
1910         DebugLn(['CheckUsesSection ',Unit_Name,'=',Flags]);
1911         {$ENDIF}
1912         Units.Add(Unit_Name+'='+Flags);
1913       end;
1914       Node:=Node.NextBrother;
1915     end;
1916   end;
1917 
1918 begin
1919   Result:=false;
1920   {$IFDEF VerboseFindUnusedUnits}
1921   DebugLn(['TStandardCodeTool.FindUnusedUnits START']);
1922   {$ENDIF}
1923   BuildTree(lsrEnd);
1924   Identifiers:=nil;
1925   try
1926     CheckUsesSection(FindMainUsesNode,false);
1927     CheckUsesSection(FindImplementationUsesNode,true);
1928   finally
1929     Identifiers.Free;
1930   end;
1931   {$IFDEF VerboseFindUnusedUnits}
1932   DebugLn(['TStandardCodeTool.FindUnusedUnits END']);
1933   {$ENDIF}
1934   Result:=true;
1935 end;
1936 
TStandardCodeTool.FindNextIncludeInInitializationnull1937 function TStandardCodeTool.FindNextIncludeInInitialization(
1938   var LinkIndex: integer): TCodeBuffer;
1939 // LinkIndex < 0  ->  search first
1940 var
1941   InitializationNode: TCodeTreeNode;
1942   StartCode: TCodeBuffer;
1943 begin
1944   Result:=nil;
1945   if LinkIndex<0 then begin
1946     BuildTree(lsrEnd);
1947     InitializationNode:=FindInitializationNode;
1948     if InitializationNode=nil then exit;
1949     LinkIndex:=Scanner.LinkIndexAtCleanPos(InitializationNode.StartPos);
1950   end else begin
1951     InitializationNode:=nil;
1952     inc(LinkIndex);
1953   end;
1954   if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) then exit;
1955   StartCode:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
1956   // ToDo: Fix the test for InitializationNode, it can be Nil.
1957   while (LinkIndex<Scanner.LinkCount)
1958   and (Scanner.Links[LinkIndex].CleanedPos<InitializationNode.EndPos) do begin
1959     Result:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
1960     if (Result<>StartCode) then
1961       exit;
1962     inc(LinkIndex);
1963   end;
1964   Result:=nil;
1965 end;
1966 
FindLazarusResourceInBuffernull1967 function TStandardCodeTool.FindLazarusResourceInBuffer(
1968   ResourceCode: TCodeBuffer; const ResourceName: string): TAtomPosition;
1969 var ResNameCode: string;
1970 
ReadLazResourcenull1971   function ReadLazResource: boolean;
1972   begin
1973     Result:=false;
1974     if not ReadNextAtomIsChar('.') then exit;
1975     if not ReadNextUpAtomIs('ADD') then exit;
1976     if not ReadNextAtomIsChar('(') then exit;
1977     ReadNextAtom;
1978     if not AtomIsStringConstant then exit;
1979     if UpAtomIs(ResNameCode) then
1980       Result:=true;
1981     repeat
1982       ReadNextAtom;
1983     until (CurPos.StartPos>SrcLen) or (AtomIsChar(')'));
1984     ReadNextAtom; // read ';'
1985   end;
1986 
1987 var CleanPos, MaxCleanPos: integer;
1988 begin
1989   Result.StartPos:=-1;
1990   if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255) then
1991     exit;
1992   if Scanner.CursorToCleanPos(1,ResourceCode,CleanPos)<>0 then exit;
1993   if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
1994     MaxCleanPos)<>0 then
1995     MaxCleanPos:=-1;
1996   MoveCursorToCleanPos(CleanPos);
1997   ResNameCode:=''''+UpperCaseStr(ResourceName)+'''';
1998   // search "LazarusResources.Add('<ResourceName>',"
1999   repeat
2000     ReadNextAtom; // read 'LazarusResources'
2001     if UpAtomIs('LAZARUSRESOURCES') then begin
2002       Result.StartPos:=CurPos.StartPos;
2003       if ReadLazResource then begin
2004         Result.EndPos:=CurPos.EndPos;
2005         exit;
2006       end;
2007     end;
2008   until (CurPos.StartPos>SrcLen) or UpAtomIs('END')
2009   or ((MaxCleanPos>0) and (CurPos.StartPos>MaxCleanPos));
2010   Result.StartPos:=-1;
2011 end;
2012 
TStandardCodeTool.FindLazarusResourcenull2013 function TStandardCodeTool.FindLazarusResource(
2014   const ResourceName: string): TAtomPosition;
2015 // search Resource in all include files
2016 var LinkIndex: integer;
2017   CurCode: TCodeBuffer;
2018 begin
2019   Result.StartPos:=-1;
2020   Result.EndPos:=-1;
2021   Result.Flag:=cafNone;
2022   LinkIndex:=-1;
2023   CurCode:=FindNextIncludeInInitialization(LinkIndex);
2024   while (CurCode<>nil) do begin
2025     Result:=FindLazarusResourceInBuffer(CurCode,ResourceName);
2026     if Result.StartPos>0 then exit;
2027     CurCode:=FindNextIncludeInInitialization(LinkIndex);
2028   end;
2029 end;
2030 
TStandardCodeTool.AddLazarusResourcenull2031 function TStandardCodeTool.AddLazarusResource(ResourceCode: TCodeBuffer;
2032   const ResourceName, ResourceData: string;
2033   SourceChangeCache: TSourceChangeCache): boolean;
2034 // ResoureData is the complete LazarusResource Statement
2035 var FromPos, ToPos, i: integer;
2036   OldPosition: TAtomPosition;
2037 begin
2038   Result:=false;
2039   if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
2040   or (ResourceData='') or (SourceChangeCache=nil) then exit;
2041   BuildTree(lsrEnd);
2042   SourceChangeCache.MainScanner:=Scanner;
2043   OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
2044   if OldPosition.StartPos>0 then begin
2045     // replace old resource
2046     FromPos:=OldPosition.StartPos;
2047     ToPos:=OldPosition.EndPos;
2048     if not SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
2049       ResourceData) then exit;
2050   end else begin
2051     // insert new resource
2052     if ResourceCode.SourceLength>0 then begin
2053       if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
2054         FromPos)<>0 then exit;
2055       inc(FromPos);
2056     end else begin
2057       // resource code empty -> can not be found in cleaned code
2058       // special replace
2059       i:=0;
2060       while (i<Scanner.LinkCount)
2061       and (Scanner.Links[i].Code<>Pointer(ResourceCode)) do
2062         inc(i);
2063       if i>=Scanner.LinkCount then exit;
2064       FromPos:=Scanner.Links[i].CleanedPos;
2065     end;
2066     if not SourceChangeCache.ReplaceEx(gtNewLine,gtNewLine,FromPos,FromPos,
2067       ResourceCode,ResourceCode.SourceLength+1,ResourceCode.SourceLength+1,
2068       ResourceData)
2069     then exit;
2070   end;
2071   if not SourceChangeCache.Apply then exit;
2072   Result:=true;
2073 end;
2074 
RemoveLazarusResourcenull2075 function TStandardCodeTool.RemoveLazarusResource(ResourceCode: TCodeBuffer;
2076   const ResourceName: string;
2077   SourceChangeCache: TSourceChangeCache): boolean;
2078 var OldPosition: TAtomPosition;
2079 begin
2080   Result:=false;
2081   if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
2082   or (SourceChangeCache=nil) then exit;
2083   BuildTree(lsrEnd);
2084   SourceChangeCache.MainScanner:=Scanner;
2085   OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
2086   if OldPosition.StartPos>0 then begin
2087     OldPosition.StartPos:=FindLineEndOrCodeInFrontOfPosition(
2088          OldPosition.StartPos);
2089     OldPosition.EndPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
2090     if not SourceChangeCache.Replace(gtNone,gtNone,
2091       OldPosition.StartPos,OldPosition.EndPos,'') then exit;
2092   end;
2093   if not SourceChangeCache.Apply then exit;
2094   Result:=true;
2095 end;
2096 
TStandardCodeTool.RenameIncludenull2097 function TStandardCodeTool.RenameInclude(LinkIndex: integer;
2098   const NewFilename: string; KeepPath: boolean;
2099   SourceChangeCache: TSourceChangeCache): boolean;
2100 { change filename in an include directive
2101   if KeepPath is true and the include dircetive contains a path
2102   (relative or absolute), then this path is kept and only the filename is
2103   replaced
2104 }
2105 var IncludeStart, IncludeEnd, FileStart, FileNameStart, FileEnd: integer;
2106 begin
2107   Result:=false;
2108   if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) or (NewFileName='')
2109   or (KeepPath and (length(NewFilename)>255))
2110   or (SourceChangeCache=nil) then exit;
2111   // find include directive
2112   IncludeEnd:=Scanner.Links[LinkIndex].CleanedPos;
2113   IncludeStart:=IncludeEnd-1;
2114   if IncludeStart<1 then exit;
2115   FileEnd:=0;
2116   case Src[IncludeStart] of
2117     '}':
2118       begin
2119         FileEnd:=IncludeStart;
2120         dec(IncludeStart);
2121         while (IncludeStart>0) and (Src[IncludeStart]<>'{') do
2122           dec(IncludeStart);
2123       end;
2124     ')':
2125       begin
2126         dec(IncludeStart);
2127         FileEnd:=IncludeStart;
2128         while (IncludeStart>1)
2129         and ((Src[IncludeStart]<>'*') or (Src[IncludeStart-1]<>'(')) do
2130           dec(IncludeStart);
2131       end;
2132     #13,#10:
2133       begin
2134         FileEnd:=IncludeStart;
2135         if (FileEnd>0) and (IsLineEndChar[Src[FileEnd]]) then dec(FileEnd);
2136         dec(IncludeStart);
2137         while (IncludeStart>1)
2138         and ((Src[IncludeStart]<>'/') or (Src[IncludeStart-1]<>'/')) do
2139           dec(IncludeStart);
2140       end;
2141   end;
2142   if IncludeStart<1 then exit;
2143   FileStart:=IncludeStart;
2144   while (FileStart<IncludeEnd) and (Src[FileStart]<>'$') do
2145     inc(FileStart);
2146   while (FileStart<IncludeEnd) and (not (IsSpaceChar[Src[FileStart]])) do
2147     inc(FileStart);
2148   while (FileStart<IncludeEnd) and (IsSpaceChar[Src[FileStart]]) do
2149     inc(FileStart);
2150   if FileStart>=IncludeEnd then exit;
2151   SourceChangeCache.MainScanner:=Scanner;
2152   if KeepPath then begin
2153     FileNameStart:=FileEnd;
2154     while (FileNameStart>FileStart) and (Src[FileNameStart]<>PathDelim) do
2155       dec(FileNameStart);
2156     if Src[FileNameStart]=PathDelim then
2157       FileStart:=FileNameStart+1;
2158   end;
2159   if not SourceChangeCache.Replace(gtNone,GtNone,FileStart,FileEnd,
2160     NewFilename) then exit;
2161   if not SourceChangeCache.Apply then exit;
2162   Result:=true;
2163 end;
2164 
TStandardCodeTool.CheckLFMnull2165 function TStandardCodeTool.CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
2166   const OnFindDefineProperty: TOnFindDefinePropertyForContext;
2167   RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
2168   ObjectsMustExist: boolean): boolean;
2169 var
2170   RootContext: TFindContext;
2171 
2172   function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
2173     const ClassContext: TFindContext; ContextIsDefault: boolean): boolean; forward;
2174 
FindNonPublishedDefinePropertynull2175   function FindNonPublishedDefineProperty(LFMNode: TLFMTreeNode;
2176     DefaultErrorPosition: integer;
2177     const IdentName: string; const ClassContext: TFindContext): boolean;
2178   // properties can be defined via DefineProperties
2179   var
2180     PropertyNode: TLFMPropertyNode;
2181     ObjectNode: TLFMObjectNode;
2182     AncestorClassContext: TFindContext;
2183     Params: TFindDeclarationParams;
2184     IsDefined: Boolean;
2185   begin
2186     Result:=false;
2187     if (not (LFMNode is TLFMPropertyNode)) then exit;
2188     PropertyNode:=TLFMPropertyNode(LFMNode);
2189     if (PropertyNode.Parent=nil)
2190     or (not (PropertyNode.Parent is TLFMObjectNode)) then exit;
2191     ObjectNode:=TLFMObjectNode(PropertyNode.Parent);
2192     // find define property
2193     IsDefined:=false;
2194     if Assigned(OnFindDefineProperty) then begin
2195       AncestorClassContext:=CleanFindContext;
2196       if ClassContext.Tool=Self then begin
2197         // the class is defined in this source
2198         // -> try to find the ancestor class
2199         if ObjectNode.AncestorContextValid then begin
2200           AncestorClassContext:=CreateFindContext(
2201                                   TFindDeclarationTool(ObjectNode.AncestorTool),
2202                                   TCodeTreeNode(ObjectNode.AncestorNode));
2203         end else begin
2204           {$IFDEF VerboseCheckLFM}
2205           debugln('FindNonPublishedDefineProperty Class is defined in this source: search ancestor ... ');
2206           {$ENDIF}
2207           Params:=TFindDeclarationParams.Create;
2208           try
2209             Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
2210                            fdfExceptionOnPredefinedIdent];
2211             Params.ContextNode:=ClassContext.Node;
2212             try
2213               if ClassContext.Tool.FindAncestorOfClass(ClassContext.Node,
2214                 Params,true) then
2215               begin
2216                 {$IFDEF VerboseCheckLFM}
2217                 debugln('FindNonPublishedDefineProperty Ancestor found');
2218                 {$ENDIF}
2219                 AncestorClassContext:=CreateFindContext(Params);
2220                 ObjectNode.AncestorTool:=AncestorClassContext.Tool;
2221                 ObjectNode.AncestorNode:=AncestorClassContext.Node;
2222               end;
2223             except
2224               // ignore search/parse errors
2225               on E: ECodeToolError do ;
2226             end;
2227           finally
2228             Params.Free;
2229           end;
2230           ObjectNode.AncestorContextValid:=true;
2231         end;
2232       end;
2233       OnFindDefineProperty(Self,ClassContext,AncestorClassContext,LFMNode,
2234         IdentName,IsDefined);
2235       if IsDefined then begin
2236         //debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' IdentName="',IdentName,'"');
2237       end else begin
2238         {$IFDEF VerboseCheckLFM}
2239         debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' NO DEFINE PROPERTIES');
2240         {$ENDIF}
2241         if DefaultErrorPosition=0 then ;
2242       end;
2243     end;
2244     Result:=IsDefined;
2245   end;
2246 
FindLFMIdentifiernull2247   function FindLFMIdentifier(LFMNode: TLFMTreeNode;
2248     DefaultErrorPosition: integer;
2249     const IdentName: string; const ClassContext: TFindContext;
2250     SearchInDefinePropertiesToo, ErrorOnNotFound: boolean;
2251     out IdentContext: TFindContext): boolean;
2252   var
2253     Params: TFindDeclarationParams;
2254     IsPublished, IsMissingInCode: Boolean;
2255     CurContext: TFindContext;
2256   begin
2257     Result:=false;
2258     IdentContext:=CleanFindContext;
2259     IsPublished:=false;
2260     if (ClassContext.Node=nil)
2261     or (not (ClassContext.Node.Desc in AllClasses)) then begin
2262       DebugLn('TStandardCodeTool.CheckLFM.FindLFMIdentifier Internal error');
2263       exit;
2264     end;
2265     Params:=TFindDeclarationParams.Create;
2266     try
2267       Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
2268                      fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
2269                      fdfIgnoreOverloadedProcs];
2270       Params.ContextNode:=ClassContext.Node;
2271       Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
2272       try
2273         {DebugLn('FindLFMIdentifier A ',
2274           ' Ident=',
2275           '"'+GetIdentifier(Params.Identifier)+'"',
2276           ' Context="'+ClassContext.Node.DescAsString,'" "',StringToPascalConst(copy(ClassContext.Tool.Src,ClassContext.Node.StartPos,20))+'"',
2277           ' File="'+ExtractFilename(ClassContext.Tool.MainFilename)+'"',
2278           ' Flags=['+FindDeclarationFlagsAsString(Params.Flags)+']'
2279           );}
2280         if ClassContext.Tool.FindIdentifierInContext(Params) then begin
2281           IdentContext:=CleanFindContext;
2282           repeat
2283             CurContext:=CreateFindContext(Params);
2284             if (not IsPublished)
2285             and (CurContext.Node.HasParentOfType(ctnClassPublished)) then
2286               IsPublished:=true;
2287 
2288             if (IdentContext.Node=nil) then begin
2289               if (LFMNode.TheType<>lfmnProperty)
2290               or ((CurContext.Node.Desc=ctnProperty)
2291                   and (not CurContext.Tool.PropNodeIsTypeLess(CurContext.Node)))
2292               then
2293                 IdentContext:=CurContext;
2294             end;
2295 
2296             if (IdentContext.Node<>nil) and IsPublished then break;
2297 
2298             // search further
2299             Params.Clear;
2300             Params.Flags:=[fdfSearchInAncestors,
2301                            fdfIgnoreMissingParams,
2302                            fdfIgnoreCurContextNode,
2303                            fdfIgnoreOverloadedProcs];
2304             Params.ContextNode:=CurContext.Node.Parent;
2305             while (Params.ContextNode<>nil)
2306             and (not (Params.ContextNode.Desc in AllClasses)) do
2307               Params.ContextNode:=Params.ContextNode.Parent;
2308             if Params.ContextNode=nil then break;
2309             Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
2310             if not CurContext.Tool.FindIdentifierInContext(Params) then
2311             begin
2312               DebugLn(['FindLFMIdentifier ERROR ancestor of '+LFMNode.GetPath+' not found: ',FindContextToString(IdentContext),' IdentName=',IdentName]);
2313               break;
2314             end;
2315           until Params.NewNode=nil;
2316         end;
2317       except
2318         // ignore search/parse errors
2319         on E: ECodeToolError do ;
2320       end;
2321     finally
2322       Params.Free;
2323     end;
2324 
2325     IsMissingInCode := False;
2326     if (IdentContext.Node<>nil) and IsPublished then begin
2327       Result:=true;
2328     end else begin
2329       // no proper node found -> search in DefineProperties
2330       if SearchInDefinePropertiesToo then begin
2331         if FindNonPublishedDefineProperty(LFMNode,DefaultErrorPosition,IdentName,ClassContext)
2332         then begin
2333           //debugln(['FindLFMIdentifier "',IdentName,'" is defined via DefineProperties']);
2334           Result:=true;
2335         end;
2336       end
2337       else
2338         IsMissingInCode := True;
2339     end;
2340     if (not Result) and ErrorOnNotFound then begin
2341       if (IdentContext.Node<>nil) and (not IsPublished) then begin
2342         LFMTree.AddError(lfmeIdentifierNotPublished,LFMNode,
2343                          'identifier '+IdentName+' is not published in class '
2344                          +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
2345                          DefaultErrorPosition);
2346       end
2347       else if IsMissingInCode then begin
2348         LFMTree.AddError(lfmeIdentifierMissingInCode,LFMNode,
2349                          'identifier '+IdentName+' not found in pascal code '
2350                          +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
2351                          DefaultErrorPosition);
2352       end
2353       else begin
2354         LFMTree.AddError(lfmeIdentifierNotFound,LFMNode,
2355                          'identifier '+IdentName+' not found in class '
2356                          +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
2357                          DefaultErrorPosition);
2358       end;
2359     end;
2360   end;
2361 
FindClassNodeForLFMObjectnull2362   function FindClassNodeForLFMObject(LFMNode: TLFMTreeNode;
2363     DefaultErrorPosition: integer;
2364     StartTool: TFindDeclarationTool; DefinitionNode: TCodeTreeNode): TFindContext;
2365   var
2366     Params: TFindDeclarationParams;
2367     Identifier: PChar;
2368     OldInput: TFindDeclarationInput;
2369   begin
2370     Result:=CleanFindContext;
2371     if (DefinitionNode.Desc=ctnIdentifier) then
2372       Identifier:=@StartTool.Src[DefinitionNode.StartPos]
2373     else if DefinitionNode.Desc=ctnProperty then
2374       Identifier:=StartTool.GetPropertyTypeIdentifier(DefinitionNode)
2375     else
2376       Identifier:=nil;
2377     if Identifier=nil then begin
2378       {$IFDEF VerboseCheckLFM}
2379       debugln(['FindClassNodeForLFMObject LFMNode=',LFMNode.GetPath,' definition node has no identifier: ',FindContextToString(CreateFindContext(StartTool,DefinitionNode))]);
2380       {$ENDIF}
2381       exit;
2382     end;
2383     Params:=TFindDeclarationParams.Create;
2384     try
2385       Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
2386         fdfSearchInParentNodes,
2387         fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
2388         fdfIgnoreOverloadedProcs,fdfIgnoreCurContextNode];
2389       Params.ContextNode:=DefinitionNode;
2390       Params.SetIdentifier(StartTool,Identifier,nil);
2391       try
2392         Params.Save(OldInput);
2393         if StartTool.FindIdentifierInContext(Params) then begin
2394           Params.Load(OldInput,true);
2395           Result:=Params.NewCodeTool.FindBaseTypeOfNode(Params,Params.NewNode);
2396           if (Result.Node=nil) then begin
2397             {$IFDEF VerboseCheckLFM}
2398             debugln(['FindClassNodeForLFMObject FindBaseTypeOfNode failed. LFMNode=',LFMNode.GetPath,' ',FindContextToString(CreateFindContext(StartTool,DefinitionNode))]);
2399             {$ENDIF}
2400             Result:=CleanFindContext;
2401           end else if (not (Result.Node.Desc in AllClasses)) then begin
2402             {$IFDEF VerboseCheckLFM}
2403             debugln(['FindClassNodeForLFMObject base type is not a class. LFMNode=',LFMNode.GetPath,' ',FindContextToString(Result)]);
2404             {$ENDIF}
2405             Result:=CleanFindContext;
2406           end;
2407         end;
2408       except
2409         // ignore search/parse errors
2410         on E: ECodeToolError do begin
2411           {$IFDEF VerboseCheckLFM}
2412           debugln(['FindClassNodeForLFMObject ',E.Message]);
2413           {$ENDIF}
2414         end;
2415       end;
2416     finally
2417       Params.Free;
2418     end;
2419     if Result.Node=nil then begin
2420       // FindClassNodeForLFMObject
2421       LFMTree.AddError(lfmeIdentifierNotFound,LFMNode,
2422                        'class '+GetIdentifier(Identifier)+' not found',
2423                        DefaultErrorPosition);
2424       exit;
2425     end;
2426   end;
2427 
CreateFootNotenull2428   function CreateFootNote(const Context: TFindContext): string;
2429   var
2430     Caret: TCodeXYPosition;
2431   begin
2432     Result:=' see '+Context.Tool.MainFilename;
2433     if Context.Tool.CleanPosToCaret(Context.Node.StartPos,Caret) then
2434       Result:=Result+'('+IntToStr(Caret.Y)+','+IntToStr(Caret.X)+')';
2435   end;
2436 
FindClassContextnull2437   function FindClassContext(const ClassName: string): TFindContext;
2438   var
2439     Params: TFindDeclarationParams;
2440     Identifier: PChar;
2441     OldInput: TFindDeclarationInput;
2442     StartTool: TStandardCodeTool;
2443   begin
2444     Result:=CleanFindContext;
2445     Params:=TFindDeclarationParams.Create;
2446     StartTool:=Self;
2447     Identifier:=PChar(Pointer(ClassName));
2448     try
2449       Params.Flags:=[fdfExceptionOnNotFound,
2450         fdfSearchInParentNodes,
2451         fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
2452         fdfIgnoreOverloadedProcs];
2453       Params.ContextNode:=FindInterfaceNode;
2454       if Params.ContextNode=nil then
2455         Params.ContextNode:=FindMainUsesNode;
2456       Params.SetIdentifier(StartTool,Identifier,nil);
2457       try
2458         Params.Save(OldInput);
2459         if FindIdentifierInContext(Params) then begin
2460           Params.Load(OldInput,true);
2461           Result:=Params.NewCodeTool.FindBaseTypeOfNode(Params,Params.NewNode);
2462           if (Result.Node=nil)
2463           or (not (Result.Node.Desc in AllClasses)) then
2464             Result:=CleanFindContext;
2465         end;
2466       except
2467         // ignore search/parse errors
2468         on E: ECodeToolError do ;
2469       end;
2470     finally
2471       Params.Free;
2472     end;
2473   end;
2474 
2475   procedure CheckLFMChildObject(LFMObject: TLFMObjectNode;
2476     const ParentContext: TFindContext; ContextIsDefault: boolean);
2477   var
2478     LFMObjectName: String;
2479     ChildContext: TFindContext;
2480     VariableTypeName: String;
2481     DefinitionNode: TCodeTreeNode;
2482     ClassContext: TFindContext;
2483     IdentifierFound: Boolean;
2484   begin
2485     // find variable for object
2486 
2487     // find identifier in Lookup Root
2488     LFMObjectName:=LFMObject.Name;
2489     //DebugLn('CheckChildObject A LFMObjectName="',LFMObjectName,'"');
2490     if LFMObjectName='' then begin
2491       LFMTree.AddError(lfmeObjectNameMissing,LFMObject,'missing object name',
2492                        LFMObject.StartPos);
2493       exit;
2494     end;
2495 
2496     ChildContext:=CleanFindContext;
2497     IdentifierFound:=(not ContextIsDefault) and
2498       FindLFMIdentifier(LFMObject,LFMObject.NamePosition,LFMObjectName,RootContext,
2499           false,ObjectsMustExist,ChildContext);
2500 
2501     //debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" IdentifierFound=',IdentifierFound,' ObjectsMustExist=',ObjectsMustExist,' ',FindContextToString(ChildContext)]);
2502     if IdentifierFound and (ObjectsMustExist or (ChildContext.Node<>nil)) then
2503     begin
2504       if ChildContext.Node=nil then begin
2505         // this is an extra entry, created via DefineProperties.
2506         // There is no generic way to test such things
2507         exit;
2508       end;
2509 
2510       // check if identifier is a variable or property
2511       VariableTypeName:='';
2512       if (ChildContext.Node.Desc=ctnVarDefinition) then begin
2513         DefinitionNode:=ChildContext.Tool.FindTypeNodeOfDefinition(ChildContext.Node);
2514         if DefinitionNode<>nil then
2515           VariableTypeName:=ChildContext.Tool.ExtractDefinitionNodeType(ChildContext.Node);
2516       end else if (ChildContext.Node.Desc=ctnProperty) then begin
2517         DefinitionNode:=ChildContext.Node;
2518         VariableTypeName:=ChildContext.Tool.ExtractPropType(ChildContext.Node,false,false);
2519       end
2520       else
2521         DefinitionNode:=nil;
2522       if DefinitionNode=nil then begin
2523         LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
2524                          LFMObjectName+' is not a variable'
2525                          +CreateFootNote(ChildContext),
2526                          LFMObject.NamePosition);
2527         exit;
2528       end;
2529 
2530       // check if variable/property has a compatible type
2531       if (VariableTypeName<>'') then begin
2532         if (LFMObject.TypeName<>'')
2533         and (CompareIdentifiers(PChar(VariableTypeName),
2534                                 PChar(LFMObject.TypeName))<>0)
2535         then begin
2536           ChildContext.Node:=DefinitionNode;
2537           LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
2538                          VariableTypeName+' expected, but '+LFMObject.TypeName+' found.'
2539                          +CreateFootNote(ChildContext),
2540                          LFMObject.NamePosition);
2541           exit;
2542         end;
2543 
2544         // ToDo: check if variable/property type exists
2545 
2546       end;
2547 
2548 
2549       // find class node
2550       //debugln(['CheckLFMChildObject searching class node: LFMObjectName="',LFMObjectName,'" ',FindContextToString(CreateFindContext(ChildContext.Tool,DefinitionNode))]);
2551       ClassContext:=FindClassNodeForLFMObject(LFMObject,LFMObject.TypeNamePosition,
2552                                               ChildContext.Tool,DefinitionNode);
2553       //debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" class context: ',FindContextToString(ClassContext)]);
2554     end else begin
2555       // try the object type
2556       ClassContext:=FindClassContext(LFMObject.TypeName);
2557       if ClassContext.Node=nil then begin
2558         // object type not found
2559         LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
2560             'type '+LFMObject.TypeName+' not found',LFMObject.TypeNamePosition);
2561       end;
2562     end;
2563     // check child LFM nodes
2564     if ClassContext.Node<>nil then
2565       CheckLFMObjectValues(LFMObject,ClassContext,false)
2566     else
2567       CheckLFMObjectValues(LFMObject,ParentContext,true);
2568   end;
2569 
FindClassNodeForPropertyTypenull2570   function FindClassNodeForPropertyType(LFMProperty: TLFMPropertyNode;
2571     DefaultErrorPosition: integer; const PropertyContext: TFindContext): TFindContext;
2572   var
2573     Params: TFindDeclarationParams;
2574   begin
2575     Result:=CleanFindContext;
2576     Params:=TFindDeclarationParams.Create;
2577     try
2578       Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
2579         fdfSearchInParentNodes,
2580         fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
2581         fdfIgnoreOverloadedProcs];
2582       Params.ContextNode:=PropertyContext.Node;
2583       Params.SetIdentifier(PropertyContext.Tool,nil,nil);
2584       try
2585         Result:=PropertyContext.Tool.FindBaseTypeOfNode(Params,
2586                                                         PropertyContext.Node);
2587       except
2588         // ignore search/parse errors
2589         on E: ECodeToolError do ;
2590       end;
2591     finally
2592       Params.Free;
2593     end;
2594     if Result.Node=nil then begin
2595       LFMTree.AddError(lfmePropertyHasNoSubProperties,LFMProperty,
2596                        'property has no sub properties',
2597                        DefaultErrorPosition);
2598       exit;
2599     end;
2600   end;
2601 
2602   procedure CheckLFMProperty(LFMProperty: TLFMPropertyNode;
2603     const ParentContext: TFindContext);
2604   // checks properties. For example lines like 'OnShow = FormShow'
2605   // or 'VertScrollBar.Range = 29'
2606   // LFMProperty is the property node
2607   // ParentContext is the context, where properties are searched.
2608   //               This can be a class or a property.
2609   var
2610     i: Integer;
2611     CurName: string;
2612     CurPropertyContext: TFindContext;
2613     SearchContext: TFindContext;
2614   begin
2615     // find complete property name
2616     //DebugLn('CheckLFMProperty A LFMProperty Name="',LFMProperty.CompleteName,'" ParentContext=',FindContextToString(ParentContext));
2617 
2618     if LFMProperty.CompleteName='' then begin
2619       LFMTree.AddError(lfmePropertyNameMissing,LFMProperty,
2620                        'property without name',LFMProperty.StartPos);
2621       exit;
2622     end;
2623 
2624     // find every part of the property name
2625     SearchContext:=ParentContext;
2626     for i:=0 to LFMProperty.NameParts.Count-1 do begin
2627       if SearchContext.Node.Desc=ctnProperty then begin
2628         // get the type of the property and search the class node
2629         SearchContext:=FindClassNodeForPropertyType(LFMProperty,
2630           LFMProperty.NameParts.NamePositions[i],SearchContext);
2631         if SearchContext.Node=nil then exit;
2632       end;
2633 
2634       CurName:=LFMProperty.NameParts.Names[i];
2635       if not FindLFMIdentifier(LFMProperty,
2636                                LFMProperty.NameParts.NamePositions[i],
2637                                CurName,SearchContext,true,true,
2638                                CurPropertyContext)
2639       then
2640         break;
2641       if CurPropertyContext.Node=nil then begin
2642         // this is an extra entry, created via DefineProperties.
2643         // There is no generic way to test such things
2644         break;
2645       end;
2646       SearchContext:=CurPropertyContext;
2647     end;
2648     // ToDo: check value
2649   end;
2650 
CheckLFMObjectValuesnull2651   function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
2652     const ClassContext: TFindContext; ContextIsDefault: boolean): boolean;
2653   var
2654     CurLFMNode: TLFMTreeNode;
2655   begin
2656     //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues A ',LFMObject.Name,':',LFMObject.TypeName);
2657     CurLFMNode:=LFMObject.FirstChild;
2658     while CurLFMNode<>nil do begin
2659       //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues B ',CurLFMNode.ClassName);
2660       case CurLFMNode.TheType of
2661         lfmnObject:
2662           CheckLFMChildObject(TLFMObjectNode(CurLFMNode),ClassContext,ContextIsDefault);
2663         lfmnProperty:
2664           if not ContextIsDefault then
2665             CheckLFMProperty(TLFMPropertyNode(CurLFMNode),ClassContext);
2666       end;
2667       CurLFMNode:=CurLFMNode.NextSibling;
2668     end;
2669     Result:=true;
2670   end;
2671 
CheckLFMRootnull2672   function CheckLFMRoot(RootLFMNode: TLFMTreeNode): boolean;
2673   var
2674     LookupRootLFMNode: TLFMObjectNode;
2675     LookupRootTypeName: String;
2676     RootClassNode: TCodeTreeNode;
2677   begin
2678     Result:=false;
2679 
2680     //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMRoot checking root ...');
2681     // get root object node
2682     if (RootLFMNode=nil) or (not (RootLFMNode is TLFMObjectNode)) then begin
2683       LFMTree.AddError(lfmeMissingRoot,nil,'missing root object',1);
2684       exit;
2685     end;
2686     LookupRootLFMNode:=TLFMObjectNode(RootLFMNode);
2687 
2688     // get type name of root object
2689     LookupRootTypeName:=LookupRootLFMNode.TypeName;
2690     if LookupRootTypeName='' then begin
2691       LFMTree.AddError(lfmeMissingRoot,nil,'missing type of root object',1);
2692       exit;
2693     end;
2694 
2695     // find root type
2696     if RootMustBeClassInIntf then begin
2697       RootClassNode:=FindClassNodeInInterface(LookupRootTypeName,true,false,false);
2698       RootContext:=CleanFindContext;
2699       RootContext.Node:=RootClassNode;
2700       RootContext.Tool:=Self;
2701     end else if RootMustBeClassInUnit then begin
2702       RootClassNode:=FindClassNodeInUnit(LookupRootTypeName,true,false,false,false);
2703       RootContext:=CleanFindContext;
2704       RootContext.Node:=RootClassNode;
2705       RootContext.Tool:=Self;
2706     end else begin
2707       RootContext:=FindClassContext(LookupRootTypeName);
2708       RootClassNode:=RootContext.Node;
2709     end;
2710     if RootClassNode=nil then begin
2711       LFMTree.AddError(lfmeMissingRoot,LookupRootLFMNode,
2712                        'type '+LookupRootLFMNode.TypeName+' not found',
2713                        LookupRootLFMNode.TypeNamePosition);
2714       exit;
2715     end;
2716     Result:=CheckLFMObjectValues(LookupRootLFMNode,RootContext,false);
2717   end;
2718 
2719 var
2720   CurRootLFMNode: TLFMTreeNode;
2721 begin
2722   Result:=false;
2723   //DebugLn('TStandardCodeTool.CheckLFM A');
2724   // create tree from LFM file
2725   LFMTree:=DefaultLFMTrees.GetLFMTree(LFMBuf,true);
2726   ActivateGlobalWriteLock;
2727   try
2728     //DebugLn('TStandardCodeTool.CheckLFM parsing LFM ...');
2729     if not LFMTree.ParseIfNeeded then exit;
2730     // parse unit and find LookupRoot
2731     //DebugLn('TStandardCodeTool.CheckLFM parsing unit ...');
2732     BuildTree(lsrImplementationUsesSectionEnd);
2733     // find every identifier
2734     //DebugLn('TStandardCodeTool.CheckLFM checking identifiers ...');
2735     CurRootLFMNode:=LFMTree.Root;
2736     while CurRootLFMNode<>nil do begin
2737       if not CheckLFMRoot(CurRootLFMNode) then exit;
2738       CurRootLFMNode:=CurRootLFMNode.NextSibling;
2739     end;
2740   finally
2741     DeactivateGlobalWriteLock;
2742   end;
2743   Result:=LFMTree.FirstError=nil;
2744 end;
2745 
FindCreateFormStatementnull2746 function TStandardCodeTool.FindCreateFormStatement(StartPos: integer;
2747   const AClassName, AVarName: string;
2748   out Position: TAtomPosition): integer;
2749 // 0=found, -1=not found, 1=found, but wrong classname
2750 var MainBeginNode: TCodeTreeNode;
2751   ClassNameFits: boolean;
2752 begin
2753   Result:=-1;
2754   Position.StartPos:=-1;
2755   if (AClassName='') or (AVarName='') or (length(AClassName)>255)
2756   or (length(AVarName)>255) then exit;
2757   if StartPos<1 then begin
2758     BuildTree(lsrEnd);
2759     MainBeginNode:=FindMainBeginEndNode;
2760     if MainBeginNode=nil then exit;
2761     StartPos:=MainBeginNode.StartPos;
2762     if StartPos<1 then exit;
2763   end;
2764   MoveCursorToCleanPos(StartPos);
2765   repeat
2766     ReadNextAtom;
2767     if UpAtomIs('APPLICATION') then begin
2768       Position.StartPos:=CurPos.StartPos;
2769       if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('CREATEFORM')
2770       and ReadNextAtomIsChar('(') then begin
2771         ReadNextAtom;
2772         ClassNameFits:=AtomIsIdentifier(PChar(AClassName));
2773         if ReadNextAtomIsChar(',')
2774         and (ReadNextAtomIsIdentifier(PChar(AVarName)) or (AVarName='*')) then begin
2775           if ReadNextAtomIsChar(')') then ReadNextAtomIsChar(';');
2776           Position.EndPos:=CurPos.EndPos;
2777           if ClassNameFits then
2778             Result:=0
2779           else
2780             Result:=1;
2781           exit;
2782         end;
2783       end;
2784     end;
2785   until (CurPos.StartPos>SrcLen);
2786   Result:=-1;
2787 end;
2788 
TStandardCodeTool.AddCreateFormStatementnull2789 function TStandardCodeTool.AddCreateFormStatement(const AClassName,
2790   AVarName: string; SourceChangeCache: TSourceChangeCache): boolean;
2791 var MainBeginNode: TCodeTreeNode;
2792   OldPosition: TAtomPosition;
2793   FromPos, ToPos, Indent: integer;
2794   Beauty: TBeautifyCodeOptions;
2795 begin
2796   Result:=false;
2797   if (AClassName='') or (length(AClassName)>255) or (AVarName='')
2798   or (length(AVarName)>255) then exit;
2799   BuildTree(lsrEnd);
2800   MainBeginNode:=FindMainBeginEndNode;
2801   if MainBeginNode=nil then exit;
2802   Beauty:=SourceChangeCache.BeautifyCodeOptions;
2803   FromPos:=-1;
2804   if FindCreateFormStatement(MainBeginNode.StartPos,AClassName,
2805     AVarName,OldPosition)=-1
2806   then begin
2807     // does not exist -> create as last in front of 'Application.Run'
2808     MoveCursorToCleanPos(MainBeginNode.StartPos);
2809     repeat
2810       if ReadNextUpAtomIs('APPLICATION') then begin
2811         FromPos:=CurPos.StartPos;
2812         if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
2813           break;
2814         end;
2815         FromPos:=-1;
2816       end;
2817     until (CurPos.StartPos>SrcLen);
2818     if FromPos<1 then exit;
2819     SourceChangeCache.MainScanner:=Scanner;
2820     Indent:=Beauty.GetLineIndent(Src,FromPos);
2821     FromPos:=FindLineEndOrCodeInFrontOfPosition(FromPos);
2822     SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,FromPos,
2823        Beauty.BeautifyStatement(
2824          'Application.CreateForm('+AClassName+','+AVarName+');',Indent));
2825   end else begin
2826     // it exists -> replace it
2827     FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
2828     ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
2829     SourceChangeCache.MainScanner:=Scanner;
2830     SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
2831        Beauty.BeautifyStatement(
2832          'Application.CreateForm('+AClassName+','+AVarName+');',
2833          Beauty.Indent));
2834   end;
2835   Result:=SourceChangeCache.Apply;
2836 end;
2837 
TStandardCodeTool.RemoveCreateFormStatementnull2838 function TStandardCodeTool.RemoveCreateFormStatement(const AVarName: string;
2839   SourceChangeCache: TSourceChangeCache): boolean;
2840 var Position: TAtomPosition;
2841   FromPos, ToPos: integer;
2842 begin
2843   Result:=false;
2844   if FindCreateFormStatement(-1,'*',AVarName,Position)=-1 then
2845     exit;
2846   FromPos:=FindLineEndOrCodeInFrontOfPosition(Position.StartPos);
2847   ToPos:=FindLineEndOrCodeAfterPosition(Position.EndPos);
2848   SourceChangeCache.MainScanner:=Scanner;
2849   SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
2850   Result:=SourceChangeCache.Apply;
2851 end;
2852 
TStandardCodeTool.ChangeCreateFormStatementnull2853 function TStandardCodeTool.ChangeCreateFormStatement(StartPos: integer;
2854   const OldClassName, OldVarName: string;
2855   const NewClassName, NewVarName: string;
2856   OnlyIfExists: boolean; SourceChangeCache: TSourceChangeCache): boolean;
2857 var MainBeginNode: TCodeTreeNode;
2858   OldPosition: TAtomPosition;
2859   FromPos, ToPos: integer;
2860   Beauty: TBeautifyCodeOptions;
2861 begin
2862   Result:=false;
2863   if (OldClassName='') or (length(OldClassName)>255)
2864   or (OldVarName='') or (length(OldVarName)>255)
2865   or (NewClassName='') or (length(NewClassName)>255)
2866   or (NewVarName='') or (length(NewVarName)>255)
2867   then exit;
2868   BuildTree(lsrEnd);
2869   Beauty:=SourceChangeCache.BeautifyCodeOptions;
2870   if StartPos<1 then begin
2871     MainBeginNode:=FindMainBeginEndNode;
2872     if MainBeginNode=nil then exit;
2873     StartPos:=MainBeginNode.StartPos;
2874     if StartPos<1 then exit;
2875   end;
2876   FromPos:=-1;
2877   if FindCreateFormStatement(StartPos,OldClassName,
2878     OldVarName,OldPosition)=-1 then begin
2879     // does not exist
2880     if OnlyIfExists then begin
2881       Result:=true;
2882       exit;
2883     end;
2884     Result:=AddCreateFormStatement(NewClassName,NewVarName,SourceChangeCache);
2885   end else begin
2886     // replace
2887     FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
2888     ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
2889     SourceChangeCache.MainScanner:=Scanner;
2890     SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
2891        Beauty.BeautifyStatement(
2892          'Application.CreateForm('+NewClassName+','+NewVarName+');',
2893          Beauty.Indent));
2894     Result:=SourceChangeCache.Apply;
2895   end;
2896 end;
2897 
ListAllCreateFormStatementsnull2898 function TStandardCodeTool.ListAllCreateFormStatements: TStrings;
2899 // list format: VarName:ClassName
2900 var Position: integer;
2901   StatementPos: TAtomPosition;
2902   s:string;
2903   var MainBeginNode: TCodeTreeNode;
2904 begin
2905   BuildTree(lsrEnd);
2906   Result:=TStringList.Create;
2907   MainBeginNode:=FindMainBeginEndNode;
2908   if MainBeginNode=nil then exit;
2909   Position:=MainBeginNode.StartPos;
2910   repeat
2911     if FindCreateFormStatement(Position,'*','*',StatementPos)=-1 then
2912       exit;
2913     Position:=StatementPos.EndPos;
2914     MoveCursorToCleanPos(StatementPos.StartPos);
2915     ReadNextAtom; // read 'Application'
2916     ReadNextAtom; // read '.'
2917     ReadNextAtom; // read 'CreateForm'
2918     ReadNextAtom; // read '('
2919     ReadNextAtom; // read class name
2920     s:=GetAtom;
2921     ReadNextAtom; // read ','
2922     ReadNextAtom; // read variable name
2923     s:=GetAtom+':'+s;
2924     Result.Add(s);
2925   until false;
2926 end;
2927 
TStandardCodeTool.SetAllCreateFromStatementsnull2928 function TStandardCodeTool.SetAllCreateFromStatements(List: TStrings;
2929   SourceChangeCache: TSourceChangeCache): boolean;
2930 { every string in the list has the format VarName:ClassName
2931   or simply VarName In the latter case it will be automatically expanded
2932   to VarName:TVarName
2933 }
2934 var Position, InsertPos, i, ColonPos, Indent: integer;
2935   StatementPos: TAtomPosition;
2936   MainBeginNode: TCodeTreeNode;
2937   AClassName, AVarName: string;
2938   LastEndPos: Integer;
2939   Beauty: TBeautifyCodeOptions;
2940 begin
2941   Result:= false;
2942   if (List = nil) or (SourceChangeCache = nil) then exit;
2943   BuildTree(lsrEnd);
2944 
2945   { first delete all CreateForm Statements }
2946   SourceChangeCache.MainScanner:= Scanner;
2947   Beauty:=SourceChangeCache.BeautifyCodeOptions;
2948   MainBeginNode:=FindMainBeginEndNode;
2949   if MainBeginNode = nil then exit;
2950   Position:=MainBeginNode.StartPos;
2951   InsertPos:=-1;
2952   LastEndPos:=-1;
2953   repeat
2954     if FindCreateFormStatement(Position, '*', '*', StatementPos) = -1 then break;
2955 
2956     Position:=StatementPos.EndPos;
2957     StatementPos.StartPos:=FindLineEndOrCodeInFrontOfPosition(StatementPos.StartPos);
2958     if (LastEndPos>0) and (StatementPos.StartPos<LastEndPos) then
2959       StatementPos.StartPos:=LastEndPos;
2960     if InsertPos < 1 then InsertPos:= StatementPos.StartPos;
2961 
2962     StatementPos.EndPos:=FindLineEndOrCodeAfterPosition(StatementPos.EndPos);
2963     LastEndPos:=StatementPos.EndPos;
2964 
2965     if not SourceChangeCache.Replace(gtNone,gtNone, StatementPos.StartPos, StatementPos.EndPos, '') then
2966       exit;
2967   until false;
2968 
2969   Result:=SourceChangeCache.Apply;
2970   if not Result then exit;
2971 
2972   { then add all CreateForm Statements }
2973   if InsertPos < 1 then begin
2974 
2975     { there was no createform statement -> insert in front of Application.Run }
2976     MoveCursorToCleanPos(MainBeginNode.StartPos);
2977     repeat
2978       if ReadNextUpAtomIs('APPLICATION') then begin
2979         InsertPos:=CurPos.StartPos;
2980         if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
2981           InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertPos);
2982           break;
2983         end;
2984         InsertPos:=-1;
2985       end;
2986     until (CurPos.StartPos>SrcLen);
2987     if InsertPos < 1 then exit;
2988   end;
2989 
2990   for i:= 0 to List.Count - 1 do begin
2991     if Length(List[i]) <= 1 then continue;
2992 
2993     ColonPos:= Pos(List[i], ':');
2994     if (ColonPos > 1) then begin
2995       AVarName:= Copy(List[i], 1, ColonPos);
2996       AClassName:= Copy(List[i], ColonPos + 1, Length(List[i]) - ColonPos);
2997     end else begin
2998       AVarName:= List[i];
2999       AClassName:= 'T' + AVarName;
3000     end;
3001     Indent:=Beauty.GetLineIndent(Src, InsertPos);
3002 
3003     SourceChangeCache.Replace(gtNewLine, gtNewLine, InsertPos, InsertPos,
3004       SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
3005         'Application.CreateForm('+AClassName+','+AVarName+');', Indent));
3006   end;
3007   Result:= Result and SourceChangeCache.Apply;
3008 end;
3009 
SetApplicationScaledStatementnull3010 function TStandardCodeTool.SetApplicationScaledStatement(
3011   const NewScaled: boolean; SourceChangeCache: TSourceChangeCache): boolean;
3012 begin
3013   Result := SetApplicationStatement('Scaled', BoolToStr(NewScaled, True), SourceChangeCache);
3014 end;
3015 
TStandardCodeTool.SetApplicationStatementnull3016 function TStandardCodeTool.SetApplicationStatement(const APropertyName,
3017   NewCode: string; SourceChangeCache: TSourceChangeCache): boolean;
3018 var
3019   StartPos, ConstStartPos, EndPos: integer;
3020   NewStatement: String;
3021   Beauty: TBeautifyCodeOptions;
3022 begin
3023   Result:=false;
3024   Beauty:=SourceChangeCache.BeautifyCodeOptions;
3025   // search old Application.APropertyName:=XYZ statement
3026   FindApplicationStatement(UpperCase(APropertyName),StartPos,ConstStartPos,EndPos);
3027   // create statement. FindApplicationStatement always returns an insert point.
3028   NewStatement:='Application.'+APropertyName+':='+NewCode+';';
3029   NewStatement:=Beauty.BeautifyStatement(NewStatement,Beauty.Indent);
3030   SourceChangeCache.MainScanner:=Scanner;
3031   if not SourceChangeCache.Replace(gtNewLine,gtNewLine,StartPos,EndPos,NewStatement) then
3032     exit;
3033   if not SourceChangeCache.Apply then exit;
3034   Result:=true;
3035 end;
3036 
FindApplicationTitleStatementnull3037 function TStandardCodeTool.FindApplicationTitleStatement(out StartPos,
3038   StringConstStartPos, EndPos: integer): boolean;
3039 begin
3040   Result := FindApplicationStatement('TITLE', StartPos, StringConstStartPos, EndPos);
3041 end;
3042 
TStandardCodeTool.GetApplicationTitleStatementnull3043 function TStandardCodeTool.GetApplicationTitleStatement(StringConstStartPos,
3044   EndPos: integer; var Title: string): boolean;
3045 var
3046   FormatStringParams: string;
3047 begin
3048   Result:=false;
3049   Title:='';
3050   if (StringConstStartPos<1) or (StringConstStartPos>SrcLen) then exit;
3051   MoveCursorToCleanPos(StringConstStartPos);
3052   ReadNextAtom;
3053   if not AtomIsStringConstant then exit;
3054   Result:=GetStringConstAsFormatString(StringConstStartPos,EndPos,Title,
3055                                        FormatStringParams);
3056   if FormatStringParams='' then ;
3057 end;
3058 
TStandardCodeTool.SetApplicationTitleStatementnull3059 function TStandardCodeTool.SetApplicationTitleStatement(const NewTitle: string;
3060   SourceChangeCache: TSourceChangeCache): boolean;
3061 begin
3062   Result := SetApplicationStatement('Title', StringToPascalConst(NewTitle), SourceChangeCache);
3063 end;
3064 
RemoveApplicationTitleStatementnull3065 function TStandardCodeTool.RemoveApplicationTitleStatement(
3066   SourceChangeCache: TSourceChangeCache): boolean;
3067 begin
3068   Result := RemoveApplicationStatement('TITLE', SourceChangeCache);
3069 end;
3070 
TStandardCodeTool.RenameFormnull3071 function TStandardCodeTool.RenameForm(const OldFormName,
3072   OldFormClassName: string; const NewFormName, NewFormClassName: string;
3073   SourceChangeCache: TSourceChangeCache): boolean;
3074 var
3075   IdentList: TStringList;
3076 begin
3077   Result:=false;
3078   if (OldFormName='') or (OldFormClassName='')
3079   or (NewFormName='') or (NewFormClassName='')
3080   or (SourceChangeCache=nil) then exit;
3081   if (OldFormName=NewFormName)
3082   and (OldFormClassName=NewFormClassName) then exit;
3083   IdentList:=TStringList.Create;
3084   try
3085     if (OldFormName<>NewFormName) then begin
3086       IdentList.Add(OldFormName);
3087       IdentList.Add(NewFormName);
3088     end;
3089     if (OldFormClassName<>NewFormClassName) then begin
3090       IdentList.Add(OldFormClassName);
3091       IdentList.Add(NewFormClassName);
3092     end;
3093     Result:=ReplaceWords(IdentList,false,SourceChangeCache);
3094   finally
3095     IdentList.Free;
3096   end;
3097 end;
3098 
TStandardCodeTool.FindFormAncestornull3099 function TStandardCodeTool.FindFormAncestor(const AClassName: string;
3100   out AncestorClassName: string): boolean;
3101 var
3102   ClassNode: TCodeTreeNode;
3103 begin
3104   Result:=false;
3105   AncestorClassName:='';
3106   if AClassName='' then exit;
3107   BuildTree(lsrImplementationStart);
3108   ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
3109   if (ClassNode=nil) then exit;
3110   // search the ancestor name
3111   MoveCursorToNodeStart(ClassNode);
3112   ReadNextAtom; // read keyword 'class', 'object', 'interface', 'dispinterface'
3113   while UpAtomIs('SEALED') or UpAtomIs('ABSTRACT') do ReadNextAtom;
3114   if UpAtomIs('PACKED') or UpAtomIs('BITPACKED') then ReadNextAtom;
3115   ReadNextAtom;
3116   if AtomIsChar('(') then begin
3117     ReadNextAtom;
3118     if UpAtomIs('SPECIALIZE') then
3119       ReadNextAtom;
3120     if AtomIsIdentifier then
3121       AncestorClassName:=GetAtom;
3122   end;
3123   if AncestorClassName='' then
3124     AncestorClassName:='TObject';
3125   Result:=true;
3126 end;
3127 
3128 {-------------------------------------------------------------------------------
3129   function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
3130     ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache): boolean;
3131 
3132   Search in all used sources (not only the cleaned source) for identifiers.
3133   It will find all identifiers, except identifiers in compiler directives.
3134   This includes identifiers in string constants and comments.
3135 
3136   ChangeStrings = true, means to replace in string constants too
3137 -------------------------------------------------------------------------------}
ReplaceWordsnull3138 function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
3139   ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
3140   SkipPointWords: boolean): boolean;
3141 
CheckIdentifiernull3142   function CheckIdentifier(const CurSource: string;
3143     IdentStart: integer): boolean;
3144   var
3145     p: integer;
3146   begin
3147     if not SkipPointWords then exit(true);
3148     p:=IdentStart-1;
3149     while (p>0) and (IsSpaceChar[CurSource[p]]) do dec(p);
3150     Result:=(p<1) or (CurSource[p]<>'.');
3151   end;
3152 
3153   procedure ReplaceWordsInSource(ACode: TCodeBuffer);
3154   var
3155     StartPos, EndPos, MaxPos, IdentStart, IdentEnd: integer;
3156     CurSource: string;
3157     i: integer;
3158   begin
3159     CurSource:=ACode.Source;
3160     MaxPos:=length(CurSource);
3161     StartPos:=1;
3162     // go through all source parts between compiler directives
3163     //DebugLn('TStandardCodeTool.ReplaceWords ',ACode.Filename);
3164     repeat
3165       EndPos:=FindNextCompilerDirective(CurSource,StartPos,
3166                                         Scanner.NestedComments);
3167       if EndPos>MaxPos then EndPos:=MaxPos+1;
3168       // search all identifiers
3169       repeat
3170         if ChangeStrings then
3171           IdentStart:=FindNextIdentifier(CurSource,StartPos,EndPos-1)
3172         else
3173           IdentStart:=FindNextIdentifierSkipStrings(CurSource,StartPos,EndPos-1);
3174         if IdentStart>=EndPos then
3175           break;
3176         i:=0;
3177         while i<IdentList.Count do begin
3178           if (IdentList[i]<>'')
3179           and (BasicCodeTools.CompareIdentifiers(PChar(Pointer(IdentList[i])),
3180                                                  @CurSource[IdentStart])=0)
3181           and CheckIdentifier(CurSource,IdentStart)
3182           and (IdentList[i]<>IdentList[i+1])
3183           then begin
3184             // identifier found -> replace
3185             IdentEnd:=IdentStart+length(IdentList[i]);
3186             //DebugLn('TStandardCodeTool.ReplaceWords replacing: ',
3187             //' "',copy(CurSource,IdentStart,IdentEnd-IdentStart),'" -> "',IdentList[i+1],'" at ',IdentStart
3188             //);
3189             SourceChangeCache.ReplaceEx(gtNone,gtNone,1,1,
3190               ACode,IdentStart,IdentEnd,IdentList[i+1]);
3191             break;
3192           end;
3193           inc(i,2);
3194         end;
3195         // skip identifier
3196         StartPos:=IdentStart;
3197         while (StartPos<MaxPos) and IsIdentChar[CurSource[StartPos]] do
3198           inc(StartPos);
3199       until false;
3200       if EndPos<=MaxPos then begin
3201         // skip comment
3202         StartPos:=FindCommentEnd(CurSource,EndPos,Scanner.NestedComments);
3203         if StartPos>MaxPos then break;
3204       end else begin
3205         break;
3206       end;
3207     until false;
3208   end;
3209 
3210 var
3211   SourceList: TFPList;
3212   i: integer;
3213 begin
3214   Result:=false;
3215   if (IdentList=nil) or (IdentList.Count=0) or (SourceChangeCache=nil)
3216   or (Odd(IdentList.Count)) then exit;
3217   BuildTree(lsrEnd);
3218   if Scanner=nil then exit;
3219   SourceChangeCache.MainScanner:=Scanner;
3220   SourceList:=TFPList.Create;
3221   try
3222     Scanner.FindCodeInRange(1,SrcLen,SourceList);
3223     for i:=0 to SourceList.Count-1 do begin
3224       ReplaceWordsInSource(TCodeBuffer(SourceList[i]));
3225     end;
3226   finally
3227     SourceList.Free;
3228   end;
3229   if not SourceChangeCache.Apply then exit;
3230   Result:=true;
3231 end;
3232 
TStandardCodeTool.FindNearestIdentifierNodenull3233 function TStandardCodeTool.FindNearestIdentifierNode(
3234   const CursorPos: TCodeXYPosition; IdentTree: TAVLTree): TAVLTreeNode;
3235 var
3236   CleanCursorPos: integer;
3237   BestDiff: Integer;
3238   CurIdentNode: TAVLTreeNode;
3239   CurDiff: Integer;
3240 begin
3241   Result:=nil;
3242   if IdentTree=nil then exit;
3243   BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,[]);
3244   BestDiff:=SrcLen+1;
3245   MoveCursorToCleanPos(1);
3246   repeat
3247     ReadNextAtom;
3248     if AtomIsIdentifier then begin
3249       CurIdentNode:=
3250         IdentTree.FindKey(@Src[CurPos.StartPos],
3251                           TListSortCompare(@CompareIdentifiers));
3252       if CurIdentNode<>nil then begin
3253         CurDiff:=CurPos.StartPos-CleanCursorPos;
3254         if CurDiff<0 then CurDiff:=-CurDiff;
3255         if (Result=nil) or (CurDiff<BestDiff) then begin
3256           BestDiff:=CurDiff;
3257           Result:=CurIdentNode;
3258         end;
3259       end;
3260     end;
3261   until CurPos.EndPos>SrcLen;
3262 end;
3263 
ReplaceWordnull3264 function TStandardCodeTool.ReplaceWord(const OldWord, NewWord: string;
3265   ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
3266   SkipPointWords: boolean): boolean;
3267 var
3268   IdentList: TStringList;
3269 begin
3270   Result:=false;
3271   if OldWord='' then exit;
3272   if OldWord=NewWord then exit(true);
3273   if (SourceChangeCache=nil) then exit;
3274   IdentList:=TStringList.Create;
3275   try
3276     IdentList.Add(OldWord);
3277     IdentList.Add(NewWord);
3278     Result:=ReplaceWords(IdentList,ChangeStrings,SourceChangeCache,SkipPointWords);
3279   finally
3280     IdentList.Free;
3281   end;
3282 end;
3283 
TStandardCodeTool.CommentCodenull3284 function TStandardCodeTool.CommentCode(const StartPos, EndPos: integer;
3285   SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;
3286 var
3287   i: LongInt;
3288   CurStartPos: LongInt;
3289   CommentNeeded: Boolean;
3290   CurEndPos: LongInt;
3291 begin
3292   if StartPos>=EndPos then
3293     RaiseException(20170421201123,'TStandardCodeTool CommentCode');
3294 
3295   Result:=false;
3296   // comment with curly brackets {}
3297   i:=StartPos;
3298   CurStartPos:=i;
3299   CurEndPos:=CurStartPos;
3300   CommentNeeded:=false;
3301   repeat
3302     //debugln(['TPascalReaderTool.CommentCode ',dbgstr(Src[i]),' Needed=',CommentNeeded,' ',dbgstr(copy(Src,CurStartPos,CurEndPos-CurStartPos))]);
3303     if (Src[i]='{') or (i>=EndPos) then begin
3304       // the area contains a comment -> comment in front
3305       if CommentNeeded then begin
3306         if not SourceChangeCache.Replace(gtNone,gtNone,
3307           CurStartPos,CurStartPos,'{') then exit;
3308         if not SourceChangeCache.Replace(gtNone,gtNone,
3309           CurEndPos,CurEndPos,'}') then exit;
3310         //DebugLn('Comment "',copy(Src,CurStartPos,i-CurStartPos),'"');
3311         CommentNeeded:=false;
3312       end;
3313       if i>=EndPos then break;
3314       // skip comment
3315       i:=FindCommentEnd(Src,i,Scanner.NestedComments)-1;
3316     end else if not IsSpaceChar[Src[i]] then begin
3317       if not CommentNeeded then begin
3318         CurStartPos:=i;
3319         CommentNeeded:=true;
3320       end;
3321       CurEndPos:=i+1;
3322     end;
3323     inc(i);
3324   until false;
3325   if Apply then
3326     Result:=SourceChangeCache.Apply
3327   else
3328     Result:=true;
3329 end;
3330 
TStandardCodeTool.GetStringConstBoundsnull3331 function TStandardCodeTool.GetStringConstBounds(
3332   const CursorPos: TCodeXYPosition;
3333   out StartPos, EndPos: TCodeXYPosition; ResolveComments: boolean): boolean;
3334 // examples:
..null3335 //   's1'+'s2'#13+AFunction(...)+inherited AMethod
3336 { $DEFINE VerboseGetStringConstBounds}
3337 type
3338   TStrConstTokenType = (scatNone, scatStrConst, scatPlus, scatIdent,
3339     scatInherited, scatPoint, scatUp,
3340     scatEdgedBracketOpen, scatEdgedBracketClose,
3341     scatRoundBracketOpen, scatRoundBracketClose);
3342 
3343   {$IFDEF VerboseGetStringConstBounds}
EnumToStrnull3344   function EnumToStr(TokenType: TStrConstTokenType): string;
3345   begin
3346     WriteStr(Result, TokenType);
3347   end;
3348   {$ENDIF}
3349 
GetCurrentTokenTypenull3350   function GetCurrentTokenType: TStrConstTokenType;
3351   begin
3352     if (CurPos.StartPos<1) or (CurPos.StartPos>SrcLen) then
3353       Result:=scatNone
3354     else if AtomIsStringConstant then
3355       Result:=scatStrConst
3356     else if AtomIsChar('+') then
3357       Result:=scatPlus
3358     else if AtomIsIdentifier then
3359       Result:=scatIdent
3360     else if UpAtomIs('INHERITED') then
3361       Result:=scatInherited
3362     else if CurPos.Flag=cafPoint then
3363       Result:=scatPoint
3364     else if AtomIsChar('^') then
3365       Result:=scatUp
3366     else if CurPos.Flag=cafRoundBracketOpen then
3367       Result:=scatRoundBracketOpen
3368     else if CurPos.Flag=cafRoundBracketClose then
3369       Result:=scatRoundBracketClose
3370     else if CurPos.Flag=cafEdgedBracketOpen then
3371       Result:=scatEdgedBracketOpen
3372     else if CurPos.Flag=cafEdgedBracketClose then
3373       Result:=scatEdgedBracketClose
3374     else
3375       Result:=scatNone;
3376   end;
3377 
3378 var
3379   CleanCursorPos: integer;
3380   SameArea: TAtomPosition;
3381   LastToken, CurrentToken: TStrConstTokenType;
3382   StartCleanPos, EndCleanPos: integer;
3383   StringConstantFound: Boolean;
3384 begin
3385   StartPos:=CursorPos;
3386   EndPos:=CursorPos;
3387   Result:=true;
3388   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
3389   {$IFDEF VerboseGetStringConstBounds}
3390   DebugLn('TStandardCodeTool.GetStringConstBounds A Start at ',CleanPosToStr(CleanCursorPos),' "',copy(Src,CleanCursorPos-5,5),'" | "',copy(Src,CleanCursorPos,5),'"');
3391   {$ENDIF}
3392   GetCleanPosInfo(-1,CleanCursorPos,ResolveComments,SameArea);
3393   {$IFDEF VerboseGetStringConstBounds}
3394   DebugLn('TStandardCodeTool.GetStringConstBounds B Same Area: ',CleanPosToStr(SameArea.StartPos),'-',CleanPosToStr(SameArea.EndPos),' "',copy(Src,SameArea.StartPos,SameArea.EndPos-SameArea.StartPos),'"');
3395   {$ENDIF}
3396   if (SameArea.EndPos=SameArea.StartPos) or (SameArea.StartPos>SrcLen) then
3397     exit;
3398 
3399   // read til end of string constant
3400   MoveCursorToCleanPos(SameArea.StartPos);
3401   ReadNextAtom;
3402   {$IFDEF VerboseGetStringConstBounds}
3403   DebugLn('TStandardCodeTool.GetStringConstBounds read til end of string Atom=',GetAtom);
3404   {$ENDIF}
3405   CurrentToken:=GetCurrentTokenType;
3406   if (CurrentToken=scatNone) then exit;
3407   StringConstantFound:=(CurrentToken=scatStrConst);
3408   repeat
3409     EndCleanPos:=CurPos.EndPos;
3410     ReadNextAtom;
3411     LastToken:=CurrentToken;
3412     CurrentToken:=GetCurrentTokenType;
3413     {$IFDEF VerboseGetStringConstBounds}
3414     DebugLn('TStandardCodeTool.GetStringConstBounds Read Forward: ',GetAtom,' EndCleanPos=',dbgs(EndCleanPos),
3415       ' LastToken=',EnumToStr(LastToken),
3416       ' CurrentToken=',EnumToStr(CurrentToken),' ',EnumToStr(GetCurrentTokenType));
3417     {$ENDIF}
3418     case CurrentToken of
3419     scatNone, scatEdgedBracketClose, scatRoundBracketClose:
3420       if not (LastToken in [scatStrConst,scatIdent,scatUp,
3421          scatEdgedBracketClose, scatRoundBracketClose])
3422       then
3423         exit
3424       else
3425         break;
3426 
3427     scatStrConst:
3428       if not (LastToken in [scatPlus]) then
3429         exit
3430       else
3431         StringConstantFound:=true;
3432 
3433     scatPlus:
3434       if not (LastToken in [scatStrConst, scatIdent, scatUp,
3435         scatEdgedBracketClose, scatRoundBracketClose]) then exit;
3436 
3437     scatIdent:
3438       if not (LastToken in [scatPlus, scatPoint, scatInherited]) then exit;
3439 
3440     scatInherited:
3441       if not (LastToken in [scatPlus, scatPoint]) then exit;
3442 
3443     scatPoint:
3444       if not (LastToken in [scatIdent, scatUp, scatRoundBracketClose,
3445                             scatEdgedBracketClose]) then
3446         exit;
3447 
3448     scatEdgedBracketOpen,scatRoundBracketOpen:
3449       if not (LastToken in [scatIdent, scatUp]) then
3450         exit
3451       else begin
3452         ReadTilBracketClose(true);
3453         CurrentToken:=GetCurrentTokenType;
3454       end;
3455 
3456     end;
3457   until false;
3458 
3459   // read til start of string constant
3460   MoveCursorToCleanPos(SameArea.StartPos);
3461   ReadNextAtom;
3462   {$IFDEF VerboseGetStringConstBounds}
3463   DebugLn('TStandardCodeTool.GetStringConstBounds Read til start of string ',GetAtom);
3464   {$ENDIF}
3465   CurrentToken:=GetCurrentTokenType;
3466   repeat
3467     StartCleanPos:=CurPos.StartPos;
3468     ReadPriorAtom;
3469     {$IFDEF VerboseGetStringConstBounds}
3470     DebugLn('TStandardCodeTool.GetStringConstBounds Read backward: ',GetAtom,' StartCleanPos=',dbgs(StartCleanPos));
3471     {$ENDIF}
3472     LastToken:=CurrentToken;
3473     CurrentToken:=GetCurrentTokenType;
3474     case CurrentToken of
3475     scatNone, scatEdgedBracketOpen, scatRoundBracketOpen:
3476       if not (LastToken in [scatStrConst,scatIdent,scatPlus]) then
3477         exit
3478       else
3479         break;
3480 
3481     scatStrConst:
3482       if not (LastToken in [scatPlus]) then
3483         exit
3484       else
3485         StringConstantFound:=true;
3486 
3487     scatPlus:
3488       if not (LastToken in [scatStrConst, scatIdent, scatRoundBracketOpen]) then
3489         exit;
3490 
3491     scatIdent:
3492       if not (LastToken in [scatPlus, scatPoint, scatUp, scatRoundBracketOpen,
3493         scatEdgedBracketOpen]) then exit;
3494 
3495     scatInherited:
3496       if not (LastToken in [scatIdent]) then exit;
3497 
3498     scatPoint:
3499       if not (LastToken in [scatIdent]) then exit;
3500 
3501     scatEdgedBracketClose,scatRoundBracketClose:
3502       if not (LastToken in [scatPlus, scatUp, scatPoint]) then
3503         exit
3504       else begin
3505         ReadBackTilBracketOpen(true);
3506         CurrentToken:=GetCurrentTokenType;
3507       end;
3508 
3509     end;
3510   until false;
3511 
3512   // convert start and end position
3513   {$IFDEF VerboseGetStringConstBounds}
3514   DebugLn('TStandardCodeTool.GetStringConstBounds END "',copy(Src,StartCleanPos,EndCleanPos-StartCleanPos),'" StringConstantFound=',dbgs(StringConstantFound));
3515   {$ENDIF}
3516   if not StringConstantFound then begin
3517     EndCleanPos:=StartCleanPos;
3518   end;
3519   if not CleanPosToCaret(StartCleanPos,StartPos) then exit;
3520   if not CleanPosToCaret(EndCleanPos,EndPos) then exit;
3521 
3522   Result:=true;
3523 end;
3524 
TStandardCodeTool.GetStringConstAsFormatStringnull3525 function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
3526   EndPos: integer; out FormatStringConstant, FormatParameters: string;
3527   out StartInStringConst, EndInStringConst: boolean): boolean;
3528 { Converts a string constant into the parameters for a Format call of the
3529   system unit.
3530 
3531   Examples:
3532 
3533   'Hallo'           -> "Hallo", ""
3534   'A'+IntToStr(1)   -> "A%s", "IntToStr(1)"
3535   'A%B'#13#10       -> "A%sB%s", "'%', #13#10"
3536 }
3537   procedure AddChar(c: char);
3538   begin
3539     FormatStringConstant:=FormatStringConstant+c;
3540   end;
3541 
3542   procedure AddParameter(const NewParam: string);
3543   begin
3544     FormatStringConstant:=FormatStringConstant+'%s';
3545     if FormatParameters<>'' then
3546       FormatParameters:=FormatParameters+',';
3547     FormatParameters:=FormatParameters+NewParam;
3548   end;
3549 
3550   procedure AddParameter(ParamStartPos,ParamEndPos: integer);
3551   begin
3552     AddParameter(copy(Src,ParamStartPos,ParamEndPos-ParamStartPos));
3553   end;
3554 
3555   procedure ConvertStringConstant;
3556   var
3557     APos: Integer;
3558     CharConstStart: Integer;
3559     InRange: Boolean;
3560   begin
3561     if (CurPos.StartPos<StartPos) and (CurPos.EndPos>StartPos) then
3562       StartInStringConst:=true;
3563     if (CurPos.StartPos<EndPos) and (CurPos.EndPos>EndPos) then
3564       EndInStringConst:=true;
3565 
3566     APos:=CurPos.StartPos;
3567     while APos<EndPos do begin
3568       InRange:=(APos>=StartPos);
3569       //debugln('ConvertStringConstant InRange=',dbgs(InRange),' Src[APos]=',Src[APos]);
3570       if Src[APos]='''' then begin
3571         // read string constant
3572         inc(APos);
3573         while APos<EndPos do begin
3574           InRange:=(APos>=StartPos);
3575           case Src[APos] of
3576           '''':
3577             if (APos<EndPos-1) and (Src[APos+1]='''') then begin
3578               // a double ' means a single '
3579               if InRange then begin
3580                 AddChar('''');
3581                 AddChar('''');
3582               end;
3583               inc(APos,2);
3584             end else begin
3585               // a single ' means end of string constant
3586               inc(APos);
3587               break;
3588             end;
3589           else
3590             begin
3591               // normal char
3592               if InRange then
3593                 AddChar(Src[APos]);
3594               inc(APos);
3595             end;
3596           end;
3597         end;
3598       end else if Src[APos]='#' then begin
3599         CharConstStart:=APos;
3600         InRange:=(APos+1>=StartPos);
3601         repeat
3602           // read char constant
3603           inc(APos);
3604           if APos<EndPos then begin
3605             if Src[APos-1]='#' then begin
3606               if IsNumberChar[Src[APos]] then begin
3607                 // read decimal number
3608                 while (APos<EndPos) and IsNumberChar[Src[APos]] do
3609                   inc(APos);
3610               end else if Src[APos]='$' then begin
3611                 // read hexnumber
3612                 while (APos<EndPos) and IsHexNumberChar[Src[APos]] do
3613                   inc(APos);
3614               end;
3615             end;
3616           end;
3617         until (APos>=EndPos) or (Src[APos]<>'#');
3618         if InRange then
3619           AddParameter(CharConstStart,APos);
3620       end else
3621         break;
3622     end;
3623   end;
3624 
3625   procedure ConvertOther;
3626   var
3627     ParamStartPos: Integer;
3628     ParamEndPos: Integer;
3629   begin
3630     // read till next string constant
3631     ParamStartPos:=CurPos.StartPos;
3632     ParamEndPos:=ParamStartPos;
3633     while (not AtomIsStringConstant) and (CurPos.EndPos<=EndPos) do begin
3634       if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
3635         ReadTilBracketClose(true);
3636       if not AtomIsChar('+') then ParamEndPos:=CurPos.EndPos;
3637       ReadNextAtom;
3638     end;
3639     if ParamEndPos>ParamStartPos then
3640       AddParameter(ParamStartPos,ParamEndPos);
3641     if AtomIsStringConstant then UndoReadNextAtom;
3642   end;
3643 
3644 var
3645   ANode: TCodeTreeNode;
3646   CodePosInFront: LongInt;
3647 begin
3648   Result:=false;
3649   // read string constants and convert it
3650   FormatStringConstant:='';
3651   FormatParameters:='';
3652   StartInStringConst:=false;
3653   EndInStringConst:=false;
3654   ANode:=FindDeepestNodeAtPos(StartPos,True);
3655   CodePosInFront:=ANode.StartPos;
3656   MoveCursorToCleanPos(CodePosInFront);
3657   if EndPos>SrcLen then EndPos:=SrcLen+1;
3658   repeat
3659     ReadNextAtom;
3660     //debugln('GetStringConstAsFormatString Atom=',GetAtom);
3661     if (CurPos.StartPos>=EndPos) then break;
3662     if CurPos.EndPos>StartPos then begin
3663       //debugln('GetStringConstAsFormatString Parsing...');
3664       if AtomIsStringConstant then begin
3665         // a string constant
3666         ConvertStringConstant;
3667       end else if AtomIsChar('+') then begin
3668         // simply ignore
3669       end else if (CurPos.Flag=cafRoundBracketOpen) or AtomIsIdentifier
3670       then begin
3671         // add as parameter
3672         ConvertOther;
3673       end else
3674         // string constant end
3675         break;
3676     end;
3677   until false;
3678   Result:=FormatStringConstant<>'';
3679 end;
3680 
GetStringConstAsFormatStringnull3681 function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
3682   EndPos: integer; out FormatStringConstant, FormatParameters: string
3683     ): boolean;
3684 var
3685   StartInStringConst, EndInStringConstant: boolean;
3686 begin
3687   Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
3688                        FormatParameters,StartInStringConst,EndInStringConstant);
3689   if StartInStringConst then ;
3690   if EndInStringConstant then ;
3691 end;
3692 
ExtractOperandnull3693 function TStandardCodeTool.ExtractOperand(const CursorPos: TCodeXYPosition; out
3694   Operand: string; WithPostTokens, WithAsOperator,
3695   WithoutTrailingPoints: boolean): boolean;
3696 var
3697   CleanPos: integer;
3698   StartPos: LongInt;
3699   EndPos: LongInt;
3700   Node: TCodeTreeNode;
3701 begin
3702   Result:=false;
3703   Operand:='';
3704   if CursorPos.Code.LineColIsSpace(CursorPos.Y,CursorPos.X) then exit;
3705   BuildTreeAndGetCleanPos(CursorPos,CleanPos);
3706   Node:=FindDeepestNodeAtPos(CleanPos,true);
3707   StartPos:=FindStartOfTerm(CleanPos,NodeTermInType(Node));
3708   if StartPos<1 then exit;
3709   StartPos:=FindNextNonSpace(Src,StartPos);
3710   if StartPos>CleanPos then exit;
3711   EndPos:=FindEndOfTerm(CleanPos,false,WithAsOperator);
3712   if not WithPostTokens then begin
3713     MoveCursorToCleanPos(CleanPos);
3714     ReadNextAtom;
3715     if CurPos.EndPos<EndPos then
3716       EndPos:=CurPos.EndPos;
3717   end;
3718   if EndPos<1 then exit;
3719   //DebugLn(['TStandardCodeTool.ExtractOperand "',dbgstr(copy(Src,StartPos,EndPos-StartPos)),'"']);
3720   Operand:=ExtractCode(StartPos,EndPos,[phpCommentsToSpace]);
3721   if WithoutTrailingPoints then begin
3722     while (Operand<>'') and (Operand[length(Operand)]='.') do
3723       Operand:=copy(Operand,1,length(Operand)-1);
3724   end;
3725   Result:=true;
3726 end;
3727 
FindApplicationScaledStatementnull3728 function TStandardCodeTool.FindApplicationScaledStatement(out StartPos,
3729   BooleanConstStartPos, EndPos: integer): boolean;
3730 begin
3731   Result := FindApplicationStatement('SCALED', StartPos, BooleanConstStartPos, EndPos);
3732 end;
3733 
FindApplicationStatementnull3734 function TStandardCodeTool.FindApplicationStatement(const APropertyUpCase: string;
3735   out StartPos, ConstStartPos, EndPos: integer): boolean;
3736 // Find statement "Application.APropertyUpCase:=XYZ;" and return True if found.
3737 //  Also return its positions (Start, const "XYZ" and End) in out parameters.
3738 // If not found, out parameters get a good position to insert such a statement.
3739 var
3740   MainBeginNode: TCodeTreeNode;
3741   AppPos, FirstAppPos: Integer;
3742 begin
3743   Result:=false;
3744   StartPos:=-1;
3745   ConstStartPos:=-1;
3746   EndPos:=-1;
3747   FirstAppPos:=-1;
3748   BuildTree(lsrEnd);
3749   MainBeginNode:=FindMainBeginEndNode;
3750   if (MainBeginNode=nil) or (MainBeginNode.StartPos<1) then exit;
3751   MoveCursorToCleanPos(MainBeginNode.StartPos);
3752   repeat
3753     ReadNextAtom;
3754     if UpAtomIs('APPLICATION') then
3755     begin
3756       AppPos:=CurPos.StartPos;
3757       if FirstAppPos=-1 then
3758         FirstAppPos:=AppPos;
3759       ReadNextAtom;
3760       if AtomIsChar('.') then
3761       begin                    // Application.APropertyUpCase:=XYZ;
3762         if ReadNextUpAtomIs(APropertyUpCase) and ReadNextUpAtomIs(':=') then
3763         begin
3764           StartPos:=AppPos;
3765           repeat               // read till semicolon or end
3766             ReadNextAtom;
3767             if ConstStartPos<1 then
3768               ConstStartPos:=CurPos.StartPos;
3769             EndPos:=CurPos.EndPos;
3770             if CurPos.Flag in [cafEnd,cafSemicolon] then
3771               exit(true);
3772           until CurPos.StartPos>SrcLen;
3773         end;
3774       end
3775       else                     // Application:=TMyApplication.Create(nil);
3776       if UpAtomIs(':=') and ReadNextUpAtomIs('TMYAPPLICATION')
3777       and ReadNextAtomIsChar('.') and ReadNextUpAtomIs('CREATE') then
3778         repeat                 // read till semicolon or end
3779           ReadNextAtom;
3780           StartPos:=CurPos.EndPos; // Insert point behind the TMyApplication.Create line.
3781           if CurPos.Flag in [cafEnd,cafSemicolon] then
3782             break;
3783         until CurPos.StartPos>SrcLen;
3784     end;  // UpAtomIs('APPLICATION')
3785   until (CurPos.StartPos>SrcLen);
3786   // The statement was not found. Return a good place for insertion.
3787   if StartPos=-1 then
3788     if FirstAppPos <> -1 then
3789       StartPos:=FirstAppPos // Before first Application statement if there is one
3790     else begin
3791       MoveCursorToNodeStart(MainBeginNode);
3792       ReadNextAtom;
3793       StartPos:=CurPos.EndPos; // or after the main Begin.
3794     end;
3795   EndPos:=StartPos;     // Both StartPos and EndPos return the same insert point.
3796 end;
3797 
GatherResourceStringSectionsnull3798 function TStandardCodeTool.GatherResourceStringSections(
3799   const CursorPos: TCodeXYPosition; PositionList: TCodeXYPositions): boolean;
3800 
3801   function SearchInUsesSection(UsesNode: TCodeTreeNode): boolean;
3802   var
3803     NewCodeTool: TPascalReaderTool;
3804     IntfNode: TCodeTreeNode;
3805     NewCaret: TCodeXYPosition;
3806     Node: TCodeTreeNode;
3807     AnUnitName, InFilename: string;
3808   begin
3809     Result:=false;
3810     if UsesNode=nil then exit(true);
3811     Node:=UsesNode.LastChild;
3812     while Node<>nil do begin
3813       AnUnitName:=ExtractUsedUnitName(Node,@InFilename);
3814       // open the unit
3815       NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,true);
3816       NewCodeTool.BuildTree(lsrImplementationStart);
3817       // search all resource string sections in the interface
3818       IntfNode:=NewCodeTool.FindInterfaceNode;
3819       if (IntfNode<>nil) and (IntfNode.LastChild<>nil) then begin
3820         IntfNode:=IntfNode.LastChild;
3821         while IntfNode<>nil do begin
3822           if IntfNode.Desc=ctnResStrSection then begin
3823             if not NewCodeTool.CleanPosToCaret(IntfNode.StartPos,NewCaret) then
3824               break;
3825             //DebugLn('TStandardCodeTool.GatherResourceStringSections Found Other ',NewCodeTool.MainFilename,' Y=',NewCaret.Y);
3826             PositionList.Add(NewCaret);
3827           end;
3828           IntfNode:=IntfNode.PriorBrother;
3829         end;
3830       end;
3831       Node:=Node.PriorBrother;
3832     end;
3833     Result:=true;
3834   end;
3835 
3836 var
3837   CleanCursorPos: integer;
3838   CursorNode: TCodeTreeNode;
3839   NewCaret: TCodeXYPosition;
3840   ANode: TCodeTreeNode;
3841 begin
3842   Result:=false;
3843   //DebugLn('TStandardCodeTool.GatherResourceStringSections A ');
3844   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
3845   CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
3846   PositionList.Clear;
3847   ANode:=CursorNode;
3848   while ANode<>nil do begin
3849     case ANode.Desc of
3850 
3851     ctnResStrSection:
3852       begin
3853         if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
3854         //DebugLn('TStandardCodeTool.GatherResourceStringSections Found Same Y=',NewCaret.Y);
3855         PositionList.Add(NewCaret);
3856       end;
3857 
3858     ctnUsesSection:
3859       if not SearchInUsesSection(ANode) then break;
3860 
3861     end;
3862 
3863     // go to next node
3864     if ANode.PriorBrother<>nil then begin
3865       ANode:=ANode.PriorBrother;
3866       if (ANode.Desc=ctnInterface) and (ANode.LastChild<>nil) then
3867         ANode:=ANode.LastChild;
3868     end else begin
3869       ANode:=ANode.Parent;
3870     end;
3871   end;
3872   Result:=true;
3873 end;
3874 
IdentifierExistsInResourceStringSectionnull3875 function TStandardCodeTool.IdentifierExistsInResourceStringSection(
3876   const CursorPos: TCodeXYPosition; const ResStrIdentifier: string): boolean;
3877 var
3878   CleanCursorPos: integer;
3879   ANode: TCodeTreeNode;
3880 begin
3881   Result:=false;
3882   if ResStrIdentifier='' then exit;
3883   // parse source and find clean positions
3884   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
3885   // find resource string section
3886   ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
3887   if (ANode=nil) then exit;
3888   ANode:=ANode.GetNodeOfType(ctnResStrSection);
3889   if ANode=nil then exit;
3890   // search identifier in section
3891   ANode:=ANode.FirstChild;
3892   while ANode<>nil do begin
3893     if (ANode.Desc=ctnConstDefinition)
3894     and CompareSrcIdentifiers(ANode.StartPos,PChar(ResStrIdentifier)) then begin
3895       Result:=true;
3896       exit;
3897     end;
3898     ANode:=ANode.NextBrother;
3899   end;
3900 end;
3901 
CreateIdentifierFromStringConstnull3902 function TStandardCodeTool.CreateIdentifierFromStringConst(const StartCursorPos,
3903   EndCursorPos: TCodeXYPosition; out Identifier: string;
3904   MaxLen: integer): boolean;
3905 var
3906   StartPos, EndPos: integer;
3907   Dummy: Integer;
3908   IdentStr: String;
3909   ANode: TCodeTreeNode;
3910   CodePosInFront: LongInt;
3911 begin
3912   Result:=false;
3913   if MaxLen<=0 then exit;
3914   // parse source and find clean positions
3915   BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
3916   Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
3917   if (Dummy<>0) and (Dummy<>-1) then exit;
3918   ANode:=FindDeepestNodeAtPos(StartPos,True);
3919   CodePosInFront:=ANode.StartPos;
3920   // read string constants and extract identifier characters
3921   Identifier:='';
3922   MoveCursorToCleanPos(CodePosInFront);
3923   repeat
3924     ReadNextAtom;
3925     //debugln('TStandardCodeTool.CreateIdentifierFromStringConst Atom=',GetAtom);
3926     if (CurPos.StartPos>=EndPos) then break;
3927     if AtomIsStringConstant then begin
3928       IdentStr:=ExtractIdentCharsFromStringConstant(CurPos.StartPos,
3929                                      StartPos,EndPos,MaxLen-length(Identifier));
3930       //debugln('TStandardCodeTool.CreateIdentifierFromStringConst IdentStr=',IdentStr);
3931       if (IdentStr<>'') then begin
3932         IdentStr[1]:=UpChars[IdentStr[1]];
3933         Identifier:=Identifier+IdentStr;
3934       end;
3935     end;
3936   until length(Identifier)>=MaxLen;
3937   Result:=Identifier<>'';
3938 end;
3939 
StringConstToFormatStringnull3940 function TStandardCodeTool.StringConstToFormatString(const StartCursorPos,
3941   EndCursorPos: TCodeXYPosition;
3942   out FormatStringConstant, FormatParameters: string;
3943   out StartInStringConst, EndInStringConst: boolean): boolean;
3944 var
3945   StartPos,EndPos,Dummy: Integer;
3946 begin
3947   Result:=false;
3948   // parse source and find clean positions
3949   BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
3950   Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
3951   if (Dummy<>0) and (Dummy<>-1) then exit;
3952   Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
3953                        FormatParameters,StartInStringConst,EndInStringConst);
3954 end;
3955 
HasInterfaceRegisterProcnull3956 function TStandardCodeTool.HasInterfaceRegisterProc: boolean;
3957 var
3958   ANode: TCodeTreeNode;
3959 begin
3960   Result:=false;
3961   ANode:=FindDeclarationNodeInInterface('Register',true);
3962   if ANode=nil then exit;
3963   if ANode.Desc=ctnProcedureHead then
3964     ANode:=Anode.Parent;
3965   if (ANode.Desc<>ctnProcedure) then exit;
3966   MoveCursorToNodeStart(ANode);
3967   if not ReadNextUpAtomIs('PROCEDURE') then exit;
3968   if not ReadNextUpAtomIs('REGISTER') then exit;
3969   ReadNextAtom;
3970   if CurPos.Flag<>cafSemicolon then exit;
3971   Result:=true;
3972 end;
3973 
ConvertDelphiToLazarusSourcenull3974 function TStandardCodeTool.ConvertDelphiToLazarusSource(AddLRSCode: boolean;
3975   SourceChangeCache: TSourceChangeCache): boolean;
3976 var
3977   Beauty: TBeautifyCodeOptions;
3978 
3979   function AddModeDelphiDirective: boolean;
3980   var
3981     ModeDirectivePos: integer;
3982     InsertPos: Integer;
3983   begin
3984     Result:=false;
3985     BuildTree(lsrInterfaceStart);
3986     if not FindModeDirective(false,ModeDirectivePos) then begin
3987       // add {$MODE Delphi} behind source type
3988       if Tree.Root=nil then exit;
3989       MoveCursorToNodeStart(Tree.Root);
3990       ReadNextAtom; // 'unit', 'program', ..
3991       ReadNextAtom; // name
3992       ReadNextAtom; // semicolon
3993       InsertPos:=CurPos.EndPos;
3994       SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
3995         '{$MODE Delphi}');
3996       if not SourceChangeCache.Apply then exit;
3997     end;
3998     // changing mode requires rescan
3999     BuildTree(lsrImplementationStart);
4000     Result:=true;
4001   end;
4002 
4003   function ConvertUsedUnits: boolean;
4004   // replace unit 'Windows' with 'LCLIntf' and add 'LResources'
4005   // rename 'in' filenames to case sensitive filename
4006   var
4007     NamePos, InPos: TAtomPosition;
4008   begin
4009     Result:=false;
4010     if FindUnitInAllUsesSections('WINDOWS',NamePos,InPos)
4011     and (InPos.StartPos<1) then begin
4012       if not SourceChangeCache.Replace(gtNone,gtNone,
4013                            NamePos.StartPos,NamePos.EndPos,'LCLIntf') then
4014       begin
4015         debugln('ConvertUsedUnits Unable to replace Windows with LCLIntf unit');
4016         exit;
4017       end;
4018     end;
4019     if AddLRSCode then
4020       if not AddUnitToMainUsesSection('LResources','',SourceChangeCache) then
4021       begin
4022         debugln('ConvertUsedUnits Unable to add LResources to main uses section');
4023         exit;
4024       end;
4025     if not RemoveUnitFromAllUsesSections('VARIANTS',SourceChangeCache) then
4026     begin
4027       debugln('ConvertUsedUnits Unable to remove Variants from all uses sections');
4028       exit;
4029     end;
4030     if not FixUsedUnitCase(SourceChangeCache) then
4031     begin
4032       debugln('ConvertUsedUnits Unable to fix unit filename case sensitivity in all uses sections');
4033       exit;
4034     end;
4035     Result:=true;
4036   end;
4037 
4038   function RemoveDFMResourceDirective: boolean;
4039   // remove {$R *.dfm} or {$R *.xfm} directive
4040   var
4041     ParamPos: Integer;
4042     ACleanPos: Integer;
4043     StartPos: Integer;
4044     s: String;
4045   begin
4046     Result:=false;
4047     // find $R directive
4048     ACleanPos:=1;
4049     repeat
4050       ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
4051         Scanner.NestedComments,ParamPos);
4052       if (ACleanPos<1) or (ACleanPos>SrcLen) or (ParamPos>SrcLen) then break;
4053       s:=UpperCaseStr(copy(Src,ParamPos,6));
4054       if (Src[ACleanPos]='{')
4055       and ((s='*.DFM}') or (s='*.XFM}'))
4056       then begin
4057         StartPos:=FindLineEndOrCodeInFrontOfPosition(ACleanPos,true);
4058         if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,ParamPos+6,'')
4059         then exit;
4060         break;
4061       end;
4062       ACleanPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments);
4063     until false;
4064     Result:=true;
4065   end;
4066 
4067   function AddLRSIncludeDirective: boolean;
4068   // add initialization and {$i unit.lrs} include directive
4069   var
4070     FirstInclude: TCodeBuffer;
4071     LRSFilename: String;
4072     InitializationNode: TCodeTreeNode;
4073     ImplementationNode: TCodeTreeNode;
4074     NewCode: String;
4075     InsertPos: Integer;
4076     LinkIndex: Integer;
4077   begin
4078     Result:=false;
4079     if AddLRSCode then begin
4080       LRSFilename:=ExtractFilenameOnly(MainFilename)+'.lrs';
4081       LinkIndex:=-1;
4082       FirstInclude:=FindNextIncludeInInitialization(LinkIndex);
4083       if (FirstInclude<>nil)
4084       and (CompareFilenames(FirstInclude.Filename,LRSFilename)=0) then begin
4085         // already there
4086         Result:=true;
4087         exit;
4088       end;
4089       if Tree.Root.Desc=ctnUnit then begin
4090         InitializationNode:=FindInitializationNode;
4091         NewCode:=Beauty.GetIndentStr(Beauty.Indent)
4092                  +'{$i '+LRSFilename+'}';
4093         if InitializationNode=nil then begin
4094           // add also an initialization section
4095           ImplementationNode:=FindImplementationNode;
4096           InsertPos:=ImplementationNode.EndPos;
4097           NewCode:=Beauty.BeautifyKeyWord(
4098                      'initialization')
4099                    +Beauty.LineEnd
4100                    +NewCode;
4101           if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
4102                                            InsertPos,InsertPos,
4103                                            NewCode) then exit;
4104         end else begin
4105           InsertPos:=InitializationNode.StartPos+length('initialization');
4106           if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
4107                                            InsertPos,InsertPos,
4108                                            NewCode) then exit;
4109         end;
4110       end else begin
4111         // only Units supported yet
4112         exit;
4113       end;
4114     end;
4115     Result:=true;
4116   end;
4117 
4118 begin
4119   Result:=false;
4120   if SourceChangeCache=nil then exit;
4121   SourceChangeCache.MainScanner:=Scanner;
4122   Beauty:=SourceChangeCache.BeautifyCodeOptions;
4123   DebugLn('ConvertDelphiToLazarusSource AddModeDelphiDirective');
4124   if not AddModeDelphiDirective then exit;
4125   DebugLn('ConvertDelphiToLazarusSource RemoveDFMResourceDirective');
4126   if not RemoveDFMResourceDirective then exit;
4127   DebugLn('ConvertDelphiToLazarusSource AddLRSIncludeDirective');
4128   if not AddLRSIncludeDirective then exit;
4129   DebugLn('ConvertDelphiToLazarusSource ConvertUsedUnits');
4130   if not ConvertUsedUnits then exit;
4131   DebugLn('ConvertDelphiToLazarusSource Apply');
4132   if not SourceChangeCache.Apply then exit;
4133   DebugLn('ConvertDelphiToLazarusSource END');
4134   Result:=true;
4135 end;
4136 
GetIDEDirectivesnull4137 function TStandardCodeTool.GetIDEDirectives(DirectiveList: TStrings;
4138   const Filter: TOnIDEDirectiveFilter): boolean;
4139 var
4140   StartPos: Integer;
4141   EndPos: Integer;
4142 begin
4143   Result:=false;
4144   DirectiveList.Clear;
4145   BuildTree(lsrImplementationStart);
4146   EndPos:=1;
4147   repeat
4148     StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
4149     if StartPos<1 then break;
4150     EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
4151     if (Filter=nil) or Filter(Self,StartPos,EndPos) then
4152       DirectiveList.Add(copy(Src,StartPos,EndPos-StartPos));
4153     if EndPos>SrcLen then break;
4154     StartPos:=EndPos;
4155   until false;
4156   Result:=true;
4157 end;
4158 
SetIDEDirectivesnull4159 function TStandardCodeTool.SetIDEDirectives(DirectiveList: TStrings;
4160   SourceChangeCache: TSourceChangeCache; const Filter: TOnIDEDirectiveFilter
4161   ): boolean;
4162 var
4163   InsertPos: Integer;
4164   EndPos: Integer;
4165   StartPos: Integer;
4166   InsertTxt: String;
4167   ImplementationNode: TCodeTreeNode;
4168 begin
4169   Result:=false;
4170   if SourceChangeCache=nil then exit;
4171   SourceChangeCache.MainScanner:=Scanner;
4172   BuildTree(lsrEnd);
4173 
4174   // find first old IDE directive
4175   InsertPos:=FindNextIDEDirective(Src,1,Scanner.NestedComments);
4176   if InsertPos>=1 then begin
4177     EndPos:=FindCommentEnd(Src,InsertPos,Scanner.NestedComments);
4178     if (Filter<>nil) and (not Filter(Self,InsertPos,EndPos)) then
4179       InsertPos:=0;
4180   end else
4181     InsertPos:=0;
4182 
4183   // remove all old IDE directives
4184   if InsertPos>=1 then
4185     EndPos:=InsertPos
4186   else
4187     EndPos:=1;
4188   repeat
4189     // find next IDE directive
4190     StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
4191     if StartPos<1 then break;
4192     EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
4193     if (Filter=nil) or Filter(Self,StartPos,EndPos) then begin
4194       // remove also space in front of directive
4195       while (StartPos>1) and (Src[StartPos-1] in [' ',#9]) do dec(StartPos);
4196       // remove also space behind directive
4197       while (EndPos<=SrcLen) and (Src[EndPos] in [' ',#9]) do inc(EndPos);
4198       if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13]) then begin
4199         inc(EndPos);
4200         if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13])
4201         and (Src[EndPos]<>Src[EndPos-1]) then
4202           inc(EndPos);
4203       end;
4204       // remove directive
4205       if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
4206         exit;
4207     end;
4208     if EndPos>SrcLen then break;
4209     StartPos:=EndPos;
4210   until false;
4211 
4212   // find a nice insert position
4213   ImplementationNode:=FindImplementationNode;
4214   if (ImplementationNode<>nil)
4215   and (ImplementationNode.StartPos<=InsertPos) then
4216     InsertPos:=0;
4217   if InsertPos<1 then begin
4218     // set default insert position
4219     InsertPos:=1;
4220     if (Tree<>nil) and (Tree.Root<>nil) then
4221       InsertPos:=Tree.Root.StartPos;
4222   end;
4223 
4224   // add directives
4225   InsertTxt:=ChompLineEndsAtEnd(DirectiveList.Text);
4226   if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
4227                             InsertTxt) then exit;
4228   if not SourceChangeCache.Apply then exit;
4229 
4230   Result:=true;
4231 end;
4232 
4233 procedure TStandardCodeTool.CalcMemSize(Stats: TCTMemStats);
4234 begin
4235   inherited CalcMemSize(Stats);
4236 end;
4237 
GatherResourceStringsWithValuenull4238 function TStandardCodeTool.GatherResourceStringsWithValue(
4239   const CursorPos: TCodeXYPosition; const StringValue: string;
4240   PositionList: TCodeXYPositions): boolean;
4241 
4242   procedure CompareStringConst(ANode: TCodeTreeNode);
4243   var
4244     CurValue: String;
4245     NewCaret: TCodeXYPosition;
4246   begin
4247     MoveCursorToNodeStart(ANode);
4248     ReadNextAtom; // read identifier
4249     if not AtomIsIdentifier then exit;
4250     ReadNextAtom; // read =
4251     if CurPos.Flag<>cafEqual then exit;
4252     ReadNextAtom; // read start of string constant
4253     if not AtomIsStringConstant then exit;
4254     // extract string constant value
4255     CurValue:=ReadStringConstantValue(CurPos.StartPos);
4256     if CurValue<>StringValue then exit;
4257     // values are the same
4258     // -> add it to position list
4259     // get x,y position
4260     if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
4261     //DebugLn('TStandardCodeTool.GatherResourceStringsWithValue Found ',MainFilename,' Y=',NewCaret.Y);
4262     PositionList.Add(NewCaret);
4263   end;
4264 
4265 var
4266   CleanCursorPos: integer;
4267   ANode: TCodeTreeNode;
4268 begin
4269   Result:=false;
4270   if PositionList=nil then exit;
4271   // parse source and find clean positions
4272   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
4273   // find resource string section
4274   ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
4275   if (ANode=nil) then exit;
4276   ANode:=ANode.GetNodeOfType(ctnResStrSection);
4277   if ANode=nil then exit;
4278   // search identifier in section
4279   ANode:=ANode.FirstChild;
4280   while ANode<>nil do begin
4281     if (ANode.Desc=ctnConstDefinition) then begin
4282       CompareStringConst(ANode);
4283     end;
4284     ANode:=ANode.NextBrother;
4285   end;
4286 end;
4287 
GetApplicationScaledStatementnull4288 function TStandardCodeTool.GetApplicationScaledStatement(BooleanConstStartPos,
4289   EndPos: integer; var AScaled: boolean): boolean;
4290 begin
4291   Result:=false;
4292   AScaled:=false;
4293   if (BooleanConstStartPos<1) or (BooleanConstStartPos>SrcLen) then exit;
4294   MoveCursorToCleanPos(BooleanConstStartPos);
4295   ReadNextAtom;
4296   if (EndPos>0) and (CurPos.EndPos>EndPos) then exit;
4297   if UpAtomIs('TRUE') then
4298   begin
4299     AScaled := True;
4300     Result := True;
4301   end;
4302   if UpAtomIs('FALSE') then
4303   begin
4304     AScaled := False;
4305     Result := True;
4306   end;
4307 end;
4308 
GatherResourceStringIdentsnull4309 function TStandardCodeTool.GatherResourceStringIdents(
4310   const SectionPos: TCodeXYPosition; var IdentTree: TAVLTree): boolean;
4311 var
4312   CleanCursorPos: integer;
4313   ANode: TCodeTreeNode;
4314 begin
4315   Result:=false;
4316   IdentTree:=nil;
4317   // parse source and find clean positions
4318   BuildTreeAndGetCleanPos(SectionPos,CleanCursorPos);
4319   // find resource string section
4320   ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
4321   if (ANode=nil) then exit;
4322   ANode:=ANode.GetNodeOfType(ctnResStrSection);
4323   if ANode=nil then exit;
4324   // search identifier in section
4325   ANode:=ANode.FirstChild;
4326   while ANode<>nil do begin
4327     if (ANode.Desc=ctnConstDefinition) then begin
4328       if IdentTree=nil then
4329         IdentTree:=TAVLTree.Create(TListSortCompare(@BasicCodeTools.CompareIdentifiers));
4330       IdentTree.Add(@Src[ANode.StartPos]);
4331     end;
4332     ANode:=ANode.NextBrother;
4333   end;
4334   Result:=true;
4335 end;
4336 
FindNearestResourceStringnull4337 function TStandardCodeTool.FindNearestResourceString(const CursorPos,
4338   SectionPos: TCodeXYPosition; var NearestPos: TCodeXYPosition): boolean;
4339 var
4340   CursorTool, SectionTool: TStandardCodeTool;
4341   IdentTree: TAVLTree;
4342   NearestNode: TAVLTreeNode;
4343   NearestCleanPos: Integer;
4344 begin
4345   Result:=false;
4346   NearestPos.Code:=nil;
4347   // get both codetools
4348   if not Assigned(OnGetCodeToolForBuffer) then exit;
4349   CursorTool:=
4350             TStandardCodeTool(OnGetCodeToolForBuffer(Self,CursorPos.Code,true));
4351   SectionTool:=
4352            TStandardCodeTool(OnGetCodeToolForBuffer(Self,SectionPos.Code,true));
4353   if (CursorTool=nil) or (SectionTool=nil) then exit;
4354   // get all resourcestring identifiers
4355   IdentTree:=nil;
4356   Result:=SectionTool.GatherResourceStringIdents(SectionPos,IdentTree);
4357   if IdentTree=nil then exit;
4358   try
4359     // find nearest resourcestring identifier in the cursor source
4360     NearestNode:=CursorTool.FindNearestIdentifierNode(CursorPos,IdentTree);
4361     if NearestNode=nil then exit;
4362     // convert node to cleanpos
4363     NearestCleanPos:={%H-}PtrUInt(NearestNode.Data)-{%H-}PtrUInt(@SectionTool.Src[1])+1;
4364     // convert cleanpos to caret
4365     CleanPosToCaret(NearestCleanPos,NearestPos);
4366   finally
4367     IdentTree.Free;
4368   end;
4369   Result:=true;
4370 end;
4371 
AddResourceStringnull4372 function TStandardCodeTool.AddResourceString(const SectionPos: TCodeXYPosition;
4373   const NewIdentifier, NewValue: string;
4374   InsertPolicy: TResourcestringInsertPolicy;
4375   const NearestPos: TCodeXYPosition;
4376   SourceChangeCache: TSourceChangeCache): boolean;
4377 var
4378   CleanSectionPos: integer;
4379   ANode, SectionNode: TCodeTreeNode;
4380   Indent: Integer;
4381   InsertPos: Integer;
4382   InsertSrc: String;
4383   NearestCleanPos: integer;
4384   Beauty: TBeautifyCodeOptions;
4385 begin
4386   Result:=false;
4387   //DebugLn('TStandardCodeTool.AddResourcestring A ',NewIdentifier,'=',NewValue,' ');
4388   if (NewIdentifier='') or (length(NewIdentifier)>255) then exit;
4389   if SourceChangeCache=nil then exit;
4390   SourceChangeCache.MainScanner:=Scanner;
4391   // parse source and find clean positions
4392   //DebugLn('TStandardCodeTool.AddResourcestring B');
4393   BuildTreeAndGetCleanPos(SectionPos,CleanSectionPos);
4394   //DebugLn('TStandardCodeTool.AddResourcestring C');
4395   // find resource string section
4396   SectionNode:=FindDeepestNodeAtPos(CleanSectionPos,true);
4397   if (SectionNode=nil) then exit;
4398   SectionNode:=SectionNode.GetNodeOfType(ctnResStrSection);
4399   if SectionNode=nil then exit;
4400 
4401   Beauty:=SourceChangeCache.BeautifyCodeOptions;
4402   //DebugLn('TStandardCodeTool.AddResourcestring D SectionChilds=',SectionNode.FirstChild<>nil);
4403   // find insert position
4404   if SectionNode.FirstChild=nil then begin
4405     // no resourcestring in this section yet -> append as first child
4406     Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
4407     InsertPos:=SectionNode.StartPos+length('RESOURCESTRING');
4408   end else begin
4409     // search insert position
4410     case InsertPolicy of
4411     rsipAlphabetically:
4412       begin
4413         // insert new identifier alphabetically
4414         ANode:=SectionNode.FirstChild;
4415         while (ANode<>nil) do begin
4416           if (ANode.Desc=ctnConstDefinition)
4417           and (CompareIdentifiers(@Src[ANode.StartPos],
4418             PChar(Pointer(NewIdentifier)))<0)
4419           then
4420             break;
4421           ANode:=ANode.NextBrother;
4422         end;
4423         if ANode=nil then begin
4424           // append new identifier as last
4425           Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
4426           InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
4427         end else begin
4428           // insert in front of node
4429           Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
4430           InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
4431         end;
4432       end;
4433 
4434     rsipContext:
4435       begin
4436         // find nearest
4437         ANode:=nil;
4438         if (NearestPos.Code<>nil)
4439         and (CaretToCleanPos(NearestPos,NearestCleanPos)=0) then begin
4440           ANode:=SectionNode.FirstChild;
4441           while (ANode<>nil) do begin
4442             if (ANode.Desc=ctnConstDefinition)
4443             and (ANode.StartPos<=NearestCleanPos)
4444             and (ANode.EndPos>NearestCleanPos)
4445             then begin
4446               break;
4447             end;
4448             ANode:=ANode.NextBrother;
4449           end;
4450         end;
4451         if ANode=nil then begin
4452           // append new identifier as last
4453           Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
4454           InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
4455         end else begin
4456           // insert behind node
4457           Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
4458           InsertPos:=FindLineEndOrCodeAfterPosition(ANode.EndPos);
4459         end;
4460       end;
4461 
4462     else
4463       begin
4464         // append new identifier
4465         Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
4466         InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
4467       end;
4468     end;
4469   end;
4470 
4471   //DebugLn('TStandardCodeTool.AddResourcestring E Indent=',Indent,' InsertPos=',InsertPos,' ',copy(Src,InsertPos-9,8),'|',copy(Src,InsertPos,8));
4472   // insert
4473   InsertSrc:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
4474                      NewIdentifier+' = '+NewValue+';',Indent);
4475   //DebugLn('TStandardCodeTool.AddResourcestring F "',InsertSrc,'"');
4476   SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,InsertSrc);
4477   SourceChangeCache.Apply;
4478   Result:=true;
4479   //DebugLn('TStandardCodeTool.AddResourcestring END ',Result);
4480 end;
4481 
FindPublishedVariablenull4482 function TStandardCodeTool.FindPublishedVariable(const AClassName,
4483   AVarName: string; ExceptionOnClassNotFound: boolean): TCodeTreeNode;
4484 var ClassNode, SectionNode: TCodeTreeNode;
4485 begin
4486   Result:=nil;
4487   if (AClassName='') or (length(AClassName)>255) then
4488     RaiseExceptionFmt(20170421201129,ctsinvalidClassName, [AClassName]);
4489   if AVarName='' then exit;
4490   BuildTree(lsrImplementationStart);
4491   ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
4492   if ClassNode=nil then begin
4493     if ExceptionOnClassNotFound then
4494       RaiseExceptionFmt(20170421201136,ctsclassNotFound, [AClassName])
4495     else
4496       exit;
4497   end;
4498   SectionNode:=ClassNode.FirstChild;
4499   while (SectionNode<>nil) do begin
4500     if SectionNode.Desc=ctnClassPublished then begin
4501       Result:=SectionNode.FirstChild;
4502       while Result<>nil do begin
4503         if (Result.Desc=ctnVarDefinition) then begin
4504           MoveCursorToNodeStart(Result);
4505           if ReadNextAtomIsIdentifier(PChar(AVarName)) then
4506             exit;
4507         end;
4508         Result:=Result.NextBrother;
4509       end;
4510     end;
4511     SectionNode:=SectionNode.NextBrother;
4512   end;
4513 end;
4514 
AddPublishedVariablenull4515 function TStandardCodeTool.AddPublishedVariable(const AClassName,
4516   VarName, VarType: string; SourceChangeCache: TSourceChangeCache): boolean;
4517 var ClassNode, SectionNode: TCodeTreeNode;
4518   Indent, InsertPos: integer;
4519   Beauty: TBeautifyCodeOptions;
4520 begin
4521   Result:=false;
4522   if (AClassName='') or (length(AClassName)>255) then
4523     RaiseExceptionFmt(20170421201143,ctsinvalidClassName2, [AClassName]);
4524   if (VarName='') or (length(VarName)>255) then
4525     RaiseExceptionFmt(20170421201152,ctsinvalidVariableName, [VarName]);
4526   if (VarType='') or (length(VarType)>255) then
4527     RaiseExceptionFmt(20170421201158,ctsinvalidVariableType, [VarType]);
4528   if (SourceChangeCache=nil) then
4529     RaiseException(20170421201203,'missing SourceChangeCache');
4530   if FindPublishedVariable(AClassName,VarName,true)<>nil then
4531   begin
4532     Result:=true;
4533     exit;
4534   end;
4535   ClassNode:=FindClassNodeInInterface(AClassName,true,false,true);
4536   if ClassNode=nil then
4537     RaiseExceptionFmt(20170421201208,ctsclassNotFound, [AClassName]);
4538   SectionNode:=ClassNode.FirstChild;
4539   if (SectionNode.NextBrother<>nil)
4540   and (SectionNode.NextBrother.Desc=ctnClassPublished) then
4541     SectionNode:=SectionNode.NextBrother;
4542   SourceChangeCache.MainScanner:=Scanner;
4543   Beauty:=SourceChangeCache.BeautifyCodeOptions;
4544   if SectionNode.FirstChild<>nil then begin
4545     Indent:=Beauty.GetLineIndent(Src,SectionNode.FirstChild.StartPos);
4546   end else begin
4547     Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
4548   end;
4549   InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos);
4550   SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
4551           Beauty.BeautifyStatement(VarName+':'+VarType+';',Indent)
4552        );
4553   Result:=SourceChangeCache.Apply;
4554 end;
4555 
RemovePublishedVariablenull4556 function TStandardCodeTool.RemovePublishedVariable(const AClassName,
4557   AVarName: string; ExceptionOnClassNotFound: boolean;
4558   SourceChangeCache: TSourceChangeCache): boolean;
4559 var VarNode: TCodeTreeNode;
4560   FromPos, ToPos: integer;
4561 begin
4562   Result:=false;
4563   VarNode:=FindPublishedVariable(AClassName,AVarName,
4564                                  ExceptionOnClassNotFound);
4565   if VarNode=nil then exit;
4566   if (VarNode.PriorBrother<>nil)
4567   and (VarNode.PriorBrother.Desc=ctnVarDefinition)
4568   and (VarNode.PriorBrother.FirstChild=nil) then begin
4569     // variable definition has the form  'PriorVarName, VarName: VarType;'
4570     // or 'PriorVarName, VarName, NextVarName: VarType'
4571     // -> delete only ', VarName'
4572     MoveCursorToNodeStart(VarNode.PriorBrother);
4573     ReadNextAtom; // read 'PriorVarName'
4574     ReadNextAtom; // read ','
4575     FromPos:=CurPos.StartPos;
4576     ReadNextAtom; // read 'VarName'
4577     ReadNextAtom; // read ':'
4578     ToPos:=CurPos.StartPos;
4579   end else begin
4580     if VarNode.FirstChild<>nil then begin
4581       // variable definition has the form  'VarName: VarType;'
4582       // -> delete whole line
4583       FromPos:=FindLineEndOrCodeInFrontOfPosition(VarNode.StartPos);
4584       ToPos:=FindLineEndOrCodeAfterPosition(VarNode.EndPos);
4585       //debugln(['TStandardCodeTool.RemovePublishedVariable ',dbgstr(copy(Src,FromPos,ToPos-FromPos))]);
4586     end else begin
4587       // variable definition has the form  'VarName, NextVarName: VarType;'
4588       // -> delete only 'VarName, '
4589       FromPos:=VarNode.StartPos;
4590       ToPos:=VarNode.NextBrother.StartPos;
4591     end;
4592   end;
4593   SourceChangeCache.MainScanner:=Scanner;
4594   if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
4595   Result:=SourceChangeCache.Apply;
4596 end;
4597 
RenamePublishedVariablenull4598 function TStandardCodeTool.RenamePublishedVariable(const AClassName,
4599   AOldVarName: string; const NewVarName, VarType: shortstring;
4600   ExceptionOnClassNotFound: boolean;
4601   SourceChangeCache: TSourceChangeCache): boolean;
4602 var
4603   TypeNode, VarNode: TCodeTreeNode;
4604   ApplyNeeded: Boolean;
4605 begin
4606   Result:=false;
4607   if (NewVarName='') or (VarType='') then exit;
4608   BuildTree(lsrEnd);
4609   VarNode:=FindPublishedVariable(AClassName,AOldVarName,
4610                                  ExceptionOnClassNotFound);
4611   if VarNode<>nil then begin
4612     // old variable found
4613     // check type
4614     TypeNode:=FindTypeNodeOfDefinition(VarNode);
4615     MoveCursorToNodeStart(TypeNode);
4616     ReadNextAtom;
4617     SourceChangeCache.MainScanner:=Scanner;
4618     ApplyNeeded:=false;
4619     if (not AtomIsIdentifier(@VarType[1])) then begin
4620       // change the type
4621       ApplyNeeded:=true;
4622       if not SourceChangeCache.Replace(gtNone,gtNone,
4623         CurPos.StartPos,CurPos.EndPos,VarType)
4624       then begin
4625         RaiseException(20170421201215,'Unable to replace type');
4626       end;
4627     end;
4628     // rename variable in source
4629     if not ReplaceWord(AOldVarName,NewVarName,false,SourceChangeCache,true)
4630     then
4631       exit;
4632     Result:=(not ApplyNeeded) or SourceChangeCache.Apply;
4633   end else begin
4634     // old variable not found -> add it
4635     Result:=AddPublishedVariable(AClassName,NewVarName,VarType,
4636                                  SourceChangeCache);
4637   end;
4638 end;
4639 
GatherPublishedClassElementsnull4640 function TStandardCodeTool.GatherPublishedClassElements(
4641   const TheClassName: string;
4642   ExceptionOnClassNotFound, WithVariables, WithMethods, WithProperties,
4643   WithAncestors: boolean;
4644   out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
4645 
4646   function Add(AFindContext: PFindContext): boolean;
4647   var
4648     ClassNode: TCodeTreeNode;
4649     CurTool: TFindDeclarationTool;
4650     SectionNode: TCodeTreeNode;
4651     ANode: TCodeTreeNode;
4652     CurProcName: String;
4653     NewNodeExt: TCodeTreeNodeExtension;
4654     CurPropName: String;
4655     CurVarName: String;
4656   begin
4657     Result:=false;
4658     ClassNode:=AFindContext^.Node;
4659     if (ClassNode=nil)
4660     or (not (ClassNode.Desc in AllClasses)) then exit;
4661     CurTool:=AFindContext^.Tool;
4662     SectionNode:=ClassNode.FirstChild;
4663     while (SectionNode<>nil) do begin
4664       if SectionNode.Desc=ctnClassPublished then begin
4665         ANode:=SectionNode.FirstChild;
4666         while ANode<>nil do begin
4667           if (ANode.Desc=ctnProcedure) and WithMethods then begin
4668             CurProcName:=CurTool.ExtractProcName(ANode,[]);
4669             {$IFDEF VerboseDanglingComponentEvents}
4670             debugln('TStandardCodeTool.GatherPublishedClassElements CurProcName="',CurProcName,'"');
4671             {$ENDIF}
4672             NewNodeExt:=TCodeTreeNodeExtension.Create;
4673             with NewNodeExt do begin
4674               Node:=ANode;
4675               Txt:=CurProcName;
4676             end;
4677             TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
4678           end
4679           else if (ANode.Desc=ctnVarDefinition) and WithVariables then begin
4680             CurVarName:=CurTool.ExtractDefinitionName(ANode);
4681             NewNodeExt:=TCodeTreeNodeExtension.Create;
4682             with NewNodeExt do begin
4683               Node:=ANode;
4684               Txt:=CurVarName;
4685             end;
4686             TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
4687           end
4688           else if (ANode.Desc=ctnProperty) and WithProperties then begin
4689             CurPropName:=CurTool.ExtractPropName(ANode,false);
4690             NewNodeExt:=TCodeTreeNodeExtension.Create;
4691             with NewNodeExt do begin
4692               Node:=ANode;
4693               Txt:=CurPropName;
4694             end;
4695             TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
4696           end;
4697           ANode:=ANode.NextBrother;
4698         end;
4699       end;
4700       SectionNode:=SectionNode.NextBrother;
4701     end;
4702     Result:=true;
4703   end;
4704 
4705 var
4706   ClassNode: TCodeTreeNode;
4707   AncestorList: TFPList;// of PFindContext
4708   i: Integer;
4709 begin
4710   Result:=false;
4711   TreeOfCodeTreeNodeExtension:=nil;
4712   if (TheClassName='') or (length(TheClassName)>255) then
4713     RaiseExceptionFmt(20170421201221,ctsInvalidClassName, [TheClassName]);
4714   {$IFDEF VerboseDanglingComponentEvents}
4715   DebugLn(['TStandardCodeTool.GatherPublishedClassElements BEFORE buildtree']);
4716   {$ENDIF}
4717   BuildTree(lsrImplementationStart);
4718   {$IFDEF VerboseDanglingComponentEvents}
4719   DebugLn(['TStandardCodeTool.GatherPublishedClassElements after buildtree']);
4720   {$ENDIF}
4721   ClassNode:=FindClassNodeInInterface(TheClassName,true,false,
4722     ExceptionOnClassNotFound);
4723   if ClassNode=nil then exit;
4724   AncestorList:=nil;
4725   try
4726     if WithAncestors then begin
4727       if not FindClassAndAncestors(ClassNode,AncestorList,true) then exit;
4728     end else begin
4729       AddFindContext(AncestorList,CreateFindContext(Self,ClassNode));
4730     end;
4731     TreeOfCodeTreeNodeExtension:=TAVLTree.Create(@CompareCodeTreeNodeExt);
4732     for i:=0 to AncestorList.Count-1 do begin
4733       if not Add(PFindContext(AncestorList[i])) then exit;
4734     end;
4735     //debugln(['TStandardCodeTool.GatherPublishedClassElements END']);
4736   finally
4737     FreeListOfPFindContext(AncestorList);
4738   end;
4739   Result:=true;
4740 end;
4741 
RetypeClassVariablesnull4742 function TStandardCodeTool.RetypeClassVariables(const AClassName: string;
4743   ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
4744   SourceChangeCache: TSourceChangeCache; SearchImplementationToo: boolean): boolean;
4745 var
4746   ClassNode: TCodeTreeNode;
4747   Node: TCodeTreeNode;
4748   TypeNode: TCodeTreeNode;
4749   OldType: String;
4750   NewType: string;
4751   HasChanged: Boolean;
4752 begin
4753   Result:=false;
4754   if SearchImplementationToo then begin
4755     BuildTree(lsrEnd);
4756     ClassNode:=FindClassNodeInUnit(AClassName,true,false,false,
4757                                    ExceptionOnClassNotFound)
4758   end
4759   else begin
4760     BuildTree(lsrImplementationStart);
4761     ClassNode:=FindClassNodeInInterface(AClassName,true,false,
4762                                         ExceptionOnClassNotFound);
4763   end;
4764   if ClassNode=nil then exit;
4765   if (ListOfTypes=nil) or (ListOfTypes.Tree.Count=0) then exit(true);
4766 
4767   HasChanged:=false;
4768   Node:=ClassNode.FirstChild;
4769   while (Node<>nil) and (Node.HasAsParent(ClassNode)) do begin
4770     if (Node.Desc=ctnVarDefinition) and (Node.FirstChild<>nil) then begin
4771       TypeNode:=Node.FirstChild;
4772       if TypeNode.Desc=ctnIdentifier then begin
4773         MoveCursorToNodeStart(TypeNode);
4774         ReadNextAtom;
4775         ReadNextAtom;
4776         if CurPos.Flag=cafPoint then begin
4777           // skip unitname
4778           ReadNextAtom;
4779         end else begin
4780           UndoReadNextAtom;
4781         end;
4782         // cursor is now on identifier
4783         OldType:=GetAtom;
4784         if ListOfTypes.Contains(OldType) then begin
4785           NewType:=ListOfTypes[OldType];
4786           if OldType<>NewType then begin
4787             // change type (or case)
4788             if not HasChanged then begin
4789               HasChanged:=true;
4790               SourceChangeCache.MainScanner:=Scanner;
4791             end;
4792             if not SourceChangeCache.Replace(gtNone,gtNone,
4793               CurPos.StartPos,CurPos.EndPos,NewType)
4794             then
4795               exit(false);
4796           end;
4797         end;
4798       end;
4799       Node:=Node.NextSkipChilds;
4800     end else
4801       Node:=Node.Next;
4802   end;
4803   if HasChanged then begin
4804     if not SourceChangeCache.Apply then exit;
4805   end;
4806   Result:=true;
4807 end;
4808 
FindDanglingComponentEventsnull4809 function TStandardCodeTool.FindDanglingComponentEvents(
4810   const TheClassName: string; RootComponent: TComponent;
4811   ExceptionOnClassNotFound, SearchInAncestors: boolean; out
4812   ListOfPInstancePropInfo: TFPList;
4813   const OverrideGetMethodName: TOnGetMethodname): boolean;
4814 var
4815   PublishedMethods: TAVLTree;
4816 
4817   procedure AddDanglingEvent(Instance: TPersistent; PropInfo: PPropInfo);
4818   var
4819     NewItem: PInstancePropInfo;
4820   begin
4821     New(NewItem);
4822     NewItem^.Instance:=Instance;
4823     NewItem^.PropInfo:=PropInfo;
4824     if ListOfPInstancePropInfo=nil then ListOfPInstancePropInfo:=TFPList.Create;
4825     ListOfPInstancePropInfo.Add(NewItem);
4826     {$IFDEF VerboseDanglingComponentEvents}
4827     debugln('AddDanglingEvent ',DbgSName(Instance),' ',PropInfo^.Name);
4828     {$ENDIF}
4829   end;
4830 
4831   procedure CheckMethodsInPersistent(APersistent: TPersistent);
4832   var
4833     TypeInfo: PTypeInfo;
4834     TypeData: PTypeData;
4835     PropInfo: PPropInfo;
4836     PropList: PPropList;
4837     CurCount,i: integer;
4838     PropType: PTypeInfo;
4839     NodeExt: TCodeTreeNodeExtension;
4840     CurMethod: TMethod;
4841     CurMethodName: String;
4842     ObjValue: TObject;
4843   begin
4844     if APersistent=nil then exit;
4845     {$IFDEF VerboseDanglingComponentEvents}
4846     debugln('TStandardCodeTool.FindDanglingComponentEvents.CheckMethodsInPersistent Checking ',DbgSName(APersistent));
4847     {$ENDIF}
4848     // read all properties and remove doubles
4849     TypeInfo:=APersistent.ClassInfo;
4850     repeat
4851       // read all property infos of current class
4852       TypeData:=GetTypeData(TypeInfo);
4853       // read property count
4854       CurCount:=GetPropList(TypeInfo,PropList);
4855       try
4856         {$IFDEF VerboseDanglingComponentEvents}
4857         debugln('    UnitName=',TypeData^.UnitName,' Type=',TypeInfo^.Name,' CurPropCount=',dbgs(CurCount));
4858         {$ENDIF}
4859         // read properties
4860         for i:=0 to CurCount-1 do begin
4861           PropInfo:=PropList^[i];
4862           {$IFDEF VerboseDanglingComponentEvents}
4863           debugln('      Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name);
4864           {$ENDIF}
4865           PropType:=PropInfo^.PropType;
4866 
4867           if (PropType^.Kind=tkMethod) then begin
4868             // RTTI property is method
4869             // -> search method in source
4870             CurMethod:=GetMethodProp(APersistent,PropInfo);
4871             if (CurMethod.Data<>nil) or (CurMethod.Code<>nil) then begin
4872               if Assigned(OverrideGetMethodName) then
4873                 CurMethodName:=OverrideGetMethodName(CurMethod,RootComponent)
4874               else
4875                 CurMethodName:=OnGetMethodName(CurMethod,RootComponent);
4876               {$IFDEF VerboseDanglingComponentEvents}
4877               debugln('      Persistent ',DbgSName(APersistent),' Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name,' CurMethodName="',CurMethodName,'"');
4878               {$ENDIF}
4879               if CurMethodName<>'' then begin
4880                 NodeExt:=FindCodeTreeNodeExt(PublishedMethods,CurMethodName);
4881                 if NodeExt=nil then begin
4882                   // method not found -> dangling event
4883                   AddDanglingEvent(APersistent,PropInfo);
4884                 end;
4885               end;
4886             end;
4887           end else if (PropType^.Kind=tkClass) then begin
4888             // RTTI property is class instance
4889             ObjValue := TObject(GetObjectProp(APersistent, PropInfo));
4890             if ObjValue is TCollection then begin
4891               // collection
4892 
4893             end else if (ObjValue is TPersistent)
4894             and (not (ObjValue is TComponent)
4895                  or (csSubComponent in TComponent(ObjValue).ComponentStyle))
4896             then begin
4897               // sub persistent (e.g. Canvas.Font)
4898               //debugln(['CheckMethodsInPersistent sub persistent: ',DbgSName(ObjValue)]);
4899               CheckMethodsInPersistent(TPersistent(ObjValue));
4900             end;
4901           end;
4902        end;
4903       finally
4904         FreeMem(PropList);
4905       end;
4906       TypeInfo:=TypeData^.ParentInfo;
4907     until TypeInfo=nil;
4908   end;
4909 
4910 var
4911   i: Integer;
4912   Collector: TComponentChildCollector;
4913   AllComponents: TFPList;
4914 begin
4915   PublishedMethods:=nil;
4916   ListOfPInstancePropInfo:=nil;
4917   Collector:=nil;
4918   AllComponents:=nil;
4919   try
4920     // search all available published methods
4921     {$IFDEF VerboseDanglingComponentEvents}
4922     debugln('TStandardCodeTool.FindDanglingComponentEvents A ',MainFilename,' ',DbgSName(RootComponent));
4923     {$ENDIF}
4924     Result:=GatherPublishedClassElements(TheClassName,ExceptionOnClassNotFound,
4925                                          false,true,false,SearchInAncestors,
4926                                          PublishedMethods);
4927     if not Result then exit;
4928     // go through all components
4929     Collector:=TComponentChildCollector.Create;
4930     AllComponents:=Collector.GetComponents(RootComponent,true);
4931     for i:=0 to AllComponents.Count-1 do
4932       CheckMethodsInPersistent(TComponent(AllComponents[i]));
4933   finally
4934     Collector.Free;
4935     DisposeAVLTree(PublishedMethods);
4936   end;
4937 end;
4938 
RemoveIdentifierDefinitionnull4939 function TStandardCodeTool.RemoveIdentifierDefinition(
4940   const CursorPos: TCodeXYPosition; SourceChangeCache: TSourceChangeCache
4941   ): boolean;
4942 var
4943   CleanCursorPos: integer;
4944   Node: TCodeTreeNode;
4945   PrevSibling: TCodeTreeNode;
4946   NextSibling: TCodeTreeNode;
4947   DeleteStartPos: LongInt;
4948   DeleteEndPos: LongInt;
4949   DeleteFirstTokenOfLine: Boolean;
4950 begin
4951   Result:=false;
4952   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
4953   Node:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
4954   if Node.Desc in AllIdentifierDefinitions then begin
4955     // Examples:
4956     //   var i, X: integer;     ->  var i[, X]: integer;
4957     //   var i, X, j: integer;  ->  var i, [X, ]j: integer;
4958     //   var X, i: integer;     ->  var [X, ]i: integer;
4959     //   type X = integer;
4960     //   const X = 0;
4961     //   const X : integer = 0;
4962     PrevSibling:=nil;
4963     NextSibling:=nil;
4964     if (Node.PriorBrother<>nil) and (Node.PriorBrother.FirstChild=nil) then
4965       PrevSibling:=Node.PriorBrother;
4966     if (Node.FirstChild=nil) and (Node.NextBrother<>nil) then
4967       NextSibling:=Node.NextBrother;
4968     DeleteStartPos:=Node.StartPos;
4969     DeleteEndPos:=Node.StartPos+GetIdentLen(@Src[Node.StartPos]);
4970     if NextSibling<>nil then begin
4971       //   var i, X, j: integer;  ->  var i, [X, ]j: integer;
4972       //   var X, i: integer;     ->  var [X, ]i: integer;
4973       MoveCursorToCleanPos(Node.StartPos);
4974       ReadNextAtom;
4975       AtomIsIdentifierE;
4976       if not ReadNextAtomIsChar(',') then
4977         RaiseCharExpectedButAtomFound(20170421201227,',');
4978       DeleteEndPos:=CurPos.EndPos;
4979     end else if PrevSibling<>nil then begin
4980       // var i, X: integer;     ->  var i[, X]: integer;
4981       MoveCursorToCleanPos(PrevSibling.StartPos);
4982       ReadNextAtom;
4983       AtomIsIdentifierE;
4984       if not ReadNextAtomIsChar(',') then
4985         RaiseCharExpectedButAtomFound(20170421201233,',');
4986       DeleteStartPos:=CurPos.StartPos;
4987     end else begin
4988       // delete whole declaration
4989       if (Node.Parent.Desc in AllDefinitionSections)
4990       and (Node.PriorBrother=nil) and (Node.NextBrother=nil) then begin
4991         // delete whole section
4992         DeleteStartPos:=Node.Parent.StartPos;
4993         DeleteEndPos:=Node.Parent.EndPos;
4994       end else if Node.Parent.Desc=ctnParameterList then begin
4995         // delete whole parameter including modifier, type and default value
4996         if Node.PriorBrother<>nil then begin
4997           // ... var i: integer; var X: ... -> ... var i: integer[; var X: ...
4998           MoveCursorToCleanPos(Node.PriorBrother.EndPos);
4999           repeat
5000             ReadNextAtom;
5001             if CurPos.Flag=cafSemicolon then begin
5002               DeleteStartPos:=CurPos.EndPos;
5003               break;
5004             end;
5005           until CurPos.StartPos>=Node.StartPos;
5006         end else begin
5007           // (var X: ... -> ([; X: ...
5008           MoveCursorToCleanPos(Node.Parent.StartPos);
5009           ReadNextAtom;
5010           if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
5011             DeleteStartPos:=CurPos.EndPos;
5012         end;
5013         if Node.NextBrother<>nil then begin
5014           // ... var X: integer; var i: ... -> .. var X: integer;] var i: ...
5015           DeleteEndPos:=Node.PriorBrother.EndPos;
5016         end else begin
5017           // ... var X: integer) -> .. var X: integer])
5018           DeleteEndPos:=Node.EndPos;
5019         end;
5020       end else begin
5021         // keep section, delete whole declaration
5022         DeleteEndPos:=Node.EndPos;
5023       end;
5024     end;
5025     // include corresponding comments
5026     DeleteFirstTokenOfLine:=FindFirstNonSpaceCharInLine(Src,DeleteStartPos)=DeleteStartPos;
5027     //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition ',dbgstr(copy(Src,FindFirstNonSpaceCharInLine(Src,DeleteStartPos),10))]);
5028     DeleteEndPos:=FindLineEndOrCodeAfterPosition(DeleteEndPos,true,DeleteFirstTokenOfLine);
5029     if DeleteFirstTokenOfLine and (Src[DeleteEndPos-1] in [#10,#13]) then begin
5030       // delete first and last token of line
5031       // => remove the entire line
5032       DeleteStartPos:=GetLineStartPosition(Src,DeleteStartPos);
5033     end;
5034     //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition "',dbgstr(copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos)),'" IncludeLineEnd=',DeleteFirstTokenOfLine]);
5035 
5036     // delete
5037     SourceChangeCache.MainScanner:=Scanner;
5038     if not SourceChangeCache.Replace(gtNone,gtNone,DeleteStartPos,DeleteEndPos,'')
5039     then exit;
5040     Result:=SourceChangeCache.Apply;
5041   end;
5042 end;
5043 
InsertStatementsnull5044 function TStandardCodeTool.InsertStatements(
5045   InsertPos: TInsertStatementPosDescription; Statements: string;
5046   SourceChangeCache: TSourceChangeCache): boolean;
5047 var
5048   CleanCursorPos: integer;
5049 begin
5050   BeginParsingAndGetCleanPos(lsrEnd,InsertPos.CodeXYPos,CleanCursorPos);
5051   Result:=InsertStatements(CleanCursorPos,Statements,InsertPos.Indent,
5052     InsertPos.FrontGap,InsertPos.AfterGap,SourceChangeCache);
5053   Result:=SourceChangeCache.Apply;
5054 end;
5055 
InsertStatementsnull5056 function TStandardCodeTool.InsertStatements(CleanPos: integer;
5057   Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
5058   SourceChangeCache: TSourceChangeCache): boolean;
5059 {
5060   ToDo: check for "uses" in Statements and extend uses section
5061         e.g. "uses unit1, unit2 in 'filename'; statements
5062   ToDo: check for single statement (e.g. for .. do | dosome;) and add begin/end
5063 
5064  }
5065 var
5066   Node: TCodeTreeNode;
5067   SameArea: TAtomPosition;
5068   BeautifyFlags: TBeautifyCodeFlags;
5069 begin
5070   Node:=FindDeepestNodeAtPos(CleanPos,true);
5071   if not (Node.Desc in AllPascalStatements) then begin
5072     MoveCursorToCleanPos(CleanPos);
5073     RaiseException(20170421201247,ctsInvalidPositionForInsertionOfStatements);
5074   end;
5075   if Node.Desc=ctnBeginBlock then
5076     Node:=BuildSubTreeAndFindDeepestNodeAtPos(Node,CleanPos,true);
5077 
5078   GetCleanPosInfo(Node.StartPos,CleanPos,false,SameArea);
5079   if (SameArea.StartPos>SrcLen) or (not IsSpaceChar[Src[SameArea.StartPos]])
5080   then begin
5081     MoveCursorToCleanPos(CleanPos);
5082     RaiseException(20170421201255,ctsInvalidPositionForInsertionOfStatements);
5083   end;
5084 
5085   SourceChangeCache.MainScanner:=Scanner;
5086   BeautifyFlags:=[bcfIndentExistingLineBreaks];
5087   if FrontGap in [gtNone,gtSpace] then
5088     include(BeautifyFlags,bcfDoNotIndentFirstLine);
5089   Statements:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
5090     Statements,Indent,BeautifyFlags);
5091 
5092   Result:=SourceChangeCache.Replace(FrontGap,AfterGap,CleanPos,CleanPos,Statements);
5093 end;
5094 
FindBlockCounterPartnull5095 function TStandardCodeTool.FindBlockCounterPart(
5096   const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
5097   NewTopLine: integer; IncludeCloseToken: Boolean): boolean;
5098 // jump from bracket-open to bracket-close or 'begin' to 'end'
5099 // or 'until' to 'repeat' ...
5100 var CleanCursorPos: integer;
5101 begin
5102   Result:=false;
5103   BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
5104   // read word at cursor
5105   MoveCursorToCleanPos(CleanCursorPos);
5106   if Src[CurPos.StartPos] in ['(','[','{'] then begin
5107     // jump forward to matching bracket
5108     ReadNextAtom;
5109     if not ReadForwardTilAnyBracketClose then exit;
5110   end else if Src[CurPos.StartPos] in [')',']','}'] then begin
5111     // jump backward to matching bracket
5112     ReadNextAtom;
5113     if not ReadBackwardTilAnyBracketClose then exit;
5114   end else begin
5115     if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
5116     while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
5117       dec(CurPos.StartPos);
5118     MoveCursorToCleanPos(CurPos.StartPos);
5119     ReadNextAtom;
5120     if CurPos.EndPos=CurPos.StartPos then exit;
5121     // read till block keyword counterpart
5122     if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
5123     or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
5124       // read forward till END, FINALLY, EXCEPT
5125       ReadTilBlockEnd(true,false);
5126     end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
5127     or UpAtomIs('UNTIL') then
5128     begin
5129       // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
5130       ReadBackTilBlockEnd(true);
5131     end else
5132       exit;
5133   end;
5134   if IncludeCloseToken then
5135     ReadNextAtom;
5136   // CursorPos now contains the counter block keyword
5137   Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
5138 end;
5139 
FindBlockStartnull5140 function TStandardCodeTool.FindBlockStart(const CursorPos: TCodeXYPosition; out
5141   NewPos: TCodeXYPosition; out NewTopLine: integer; SkipStart: boolean
5142   ): boolean;
5143 // jump to beginning of current block
5144 // e.g. bracket open, 'begin', 'repeat', ...
5145 var CleanCursorPos: integer;
5146   CursorOnStart: Boolean;
5147   Node: TCodeTreeNode;
5148   MinPos: Integer;
5149 begin
5150   Result:=false;
5151   // scan code
5152   BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
5153   // read word at cursor
5154   MoveCursorToCleanPos(CleanCursorPos);
5155   while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
5156     dec(CurPos.StartPos);
5157   MoveCursorToCleanPos(CurPos.StartPos);
5158   ReadNextAtom;
5159   try
5160     if CurPos.StartPos>=SrcLen then begin
5161       ReadPriorAtom;
5162       if CurPos.StartPos<1 then begin
5163         MoveCursorToCleanPos(1);
5164         exit(true);
5165       end;
5166     end;
5167     Node:=FindDeepestNodeAtPos(CleanCursorPos,false);
5168     //if Node<>nil then debugln(['TStandardCodeTool.FindBlockStart ',Node.DescAsString]);
5169     if (Node=nil)
5170     or (Node.Desc in (AllPascalStatements+AllPascalTypes-AllClasses))
5171     or (Src[CurPos.StartPos] in [')',']','}'])
5172     then begin
5173       MinPos:=1;
5174       if Node<>nil then MinPos:=Node.StartPos;
5175       repeat
5176         //debugln(['TStandardCodeTool.FindBlockStart atom ',CleanPosToStr(CurPos.StartPos),' ',GetAtom]);
5177         if (CurPos.StartPos<0) then begin
5178           // start of source found -> this is always a block start
5179           MoveCursorToCleanPos(1);
5180           exit(true);
5181         end
5182         else if Src[CurPos.StartPos] in [')',']','}'] then begin
5183           // jump backward to matching bracket
5184           CursorOnStart:=(CleanCursorPos=CurPos.StartPos);
5185           if not ReadBackwardTilAnyBracketClose then exit;
5186           if CursorOnStart then exit(true);
5187         end
5188         else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
5189           CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
5190         begin
5191           // block start found
5192           if (CurPos.StartPos<CleanCursorPos) or (not SkipStart) then
5193             exit(true);
5194         end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
5195         or UpAtomIs('UNTIL') then
5196         begin
5197           // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
5198           CursorOnStart:=(CleanCursorPos>=CurPos.StartPos)
5199                      and (CleanCursorPos<CurPos.EndPos);
5200           ReadBackTilBlockEnd(true);
5201           if CursorOnStart then exit(true);
5202         end;
5203         ReadPriorAtom;
5204       until CurPos.StartPos<MinPos;
5205     end;
5206     if Node<>nil then begin
5207       if SkipStart and (CleanCursorPos=Node.StartPos) then begin
5208         while (Node<>nil) and (Node.StartPos=CleanCursorPos) do
5209           Node:=Node.Parent;
5210         if Node<>nil then
5211           MoveCursorToCleanPos(Node.StartPos)
5212         else
5213           MoveCursorToCleanPos(1);
5214         exit(true);
5215       end;
5216       if CleanCursorPos>=Node.StartPos then begin
5217         MoveCursorToCleanPos(Node.StartPos);
5218         exit(true);
5219       end;
5220     end;
5221   finally
5222     if Result then begin
5223       // CursorPos now contains the block start atom
5224       Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
5225     end;
5226   end;
5227 end;
5228 
GuessUnclosedBlocknull5229 function TStandardCodeTool.GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
5230   out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
5231 { search a block (e.g. begin..end) that looks unclosed, i.e. 'begin'
5232   without 'end' or 'begin' with 'end' in a different column.
5233   This function can be used as GuessNextUnclosedBlock, because it ignores blocks
5234   in front of CursorPos.
5235 
5236   Examples for good blocks:
5237 
5238     repeat
5239     until
5240 
5241     begin end           // start and end of block in the same line
5242 
5243     if expr then begin  // first char in line is relevant, not the block keyword
5244     end
5245 
5246     class;
5247 
5248 
5249   Examples for bad blocks:
5250 
5251     begin               // block start and end has different indenting
5252       end
5253 
5254     asm                 // 'end.' is source end, never asm end
5255     end.
5256 
5257       try               // different indenting
5258     finally
5259 
5260     repeat              // keywords do not match
5261     end
5262 
5263 }
5264 var CleanCursorPos: integer;
5265 begin
5266   Result:=false;
5267   BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
5268   // start reading at beginning of code
5269   MoveCursorToCleanPos(1);
5270   BuildBlockKeyWordFuncList;
5271   if ReadTilGuessedUnclosedBlock(CleanCursorPos,false) then
5272     Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
5273   //WriteDebugTreeReport;
5274 end;
5275 
FindBlockCleanBoundsnull5276 function TStandardCodeTool.FindBlockCleanBounds(
5277   const CursorPos: TCodeXYPosition; out BlockCleanStart, BlockCleanEnd: integer
5278   ): boolean;
5279 var
5280   CleanCursorPos: integer;
5281   BlockStartFound: Boolean;
5282 begin
5283   Result:=false;
5284   BlockCleanStart:=0;
5285   BlockCleanEnd:=0;
5286   // scan code
5287   BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
5288   // read word at cursor
5289   MoveCursorToCleanPos(CleanCursorPos);
5290   while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
5291     dec(CurPos.StartPos);
5292   MoveCursorToCleanPos(CurPos.StartPos);
5293   ReadNextAtom;
5294   BlockStartFound:=false;
5295   repeat
5296     ReadPriorAtom;
5297     if (CurPos.StartPos<0) then begin
5298       // start of source found -> this is always a block start
5299       MoveCursorToCleanPos(1);
5300       BlockStartFound:=true;
5301       break;
5302     end
5303     else if Src[CurPos.StartPos] in [')',']','}'] then begin
5304       // jump backward to matching bracket
5305       if not ReadBackwardTilAnyBracketClose then exit;
5306     end
5307     else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
5308       CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
5309     begin
5310       // block start found
5311       BlockStartFound:=true;
5312       break;
5313     end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
5314     or UpAtomIs('UNTIL') then
5315     begin
5316       // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
5317       ReadBackTilBlockEnd(true);
5318     end;
5319   until false;
5320   if not BlockStartFound then exit;
5321   BlockCleanStart:=CurPos.StartPos;
5322 
5323   // read word at cursor
5324   MoveCursorToCleanPos(BlockCleanStart);
5325   if Src[CurPos.StartPos] in ['(','[','{'] then begin
5326     // jump forward to matching bracket
5327     ReadNextAtom;
5328     if not ReadForwardTilAnyBracketClose then exit;
5329   end else begin
5330     if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
5331     while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
5332       dec(CurPos.StartPos);
5333     MoveCursorToCleanPos(CurPos.StartPos);
5334     ReadNextAtom;
5335     if CurPos.EndPos=CurPos.StartPos then exit;
5336     // read till block keyword counterpart
5337     if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
5338     or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
5339       // read forward till END, FINALLY, EXCEPT
5340       ReadTilBlockEnd(true,false);
5341     end else
5342       exit;
5343   end;
5344   BlockCleanEnd:=CurPos.StartPos;
5345   Result:=true;
5346 end;
5347 
CompleteBlocknull5348 function TStandardCodeTool.CompleteBlock(const CursorPos: TCodeXYPosition;
5349   SourceChangeCache: TSourceChangeCache; OnlyIfCursorBlockIndented: boolean;
5350   out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
5351 { For example:
5352   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5353     begin
5354       |
5355       ...
5356   something
5357   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5358     if then begin
5359       |
5360       ...
5361   something
5362   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5363   begin
5364     |
5365 
5366   procedure
5367   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5368 
5369   Statements:
5370     begin: end;
5371     asm: end;
5372     try: finally end;
5373     finally: end;
5374     except: end;
5375     repeat: until ;
5376     case of: end;
5377     case :: ;
5378     case else: end;
5379     (: )
5380     [: ]
5381 
5382   Types:
5383     (: )
5384     [: ]
5385     record: end;
5386     class: end;
5387     object: end;
5388     interface: end;
5389 }
5390 type
5391   TBlockType = (
5392     btNone,
5393     btBegin,
5394     btAsm,
5395     btEdgedBracket,
5396     btRoundBracket,
5397     btTry,
5398     btFinally,
5399     btExcept,
5400     btCase,
5401     btCaseOf,
5402     btCaseColon,
5403     btCaseElse,
5404     btRepeat,
5405     btIf,
5406     btIfElse,
5407     btClass,
5408     btInterface,
5409     btObject,
5410     btRecord
5411     );
5412   TBlock = record
5413     Typ: TBlockType;
5414     StartPos: integer;
5415     InnerIndent: integer;
5416     InnerStartPos: integer;
5417   end;
5418   PBlock = ^TBlock;
5419   TBlockStack = record
5420     Stack: PBlock;
5421     Capacity: integer;
5422     Top: integer;
5423   end;
5424   TExBool = (ebNone, ebTrue, ebFalse);
5425 var
5426   CleanCursorPos: integer;
5427   StartNode: TCodeTreeNode;
5428   InternalCursorAtEmptyLine: TExBool;
5429   Beauty: TBeautifyCodeOptions;
5430 
CursorAtEmptyLinenull5431   function CursorAtEmptyLine: Boolean;
5432   // true if cursor in empty line or at line end in front of an empty line
5433   var
5434     p: LongInt;
5435   begin
5436     if InternalCursorAtEmptyLine=ebNone then begin
5437       if (CleanCursorPos>SrcLen) or InEmptyLine(Src,CleanCursorPos) then
5438         InternalCursorAtEmptyLine:=ebTrue
5439       else begin
5440         p:=CleanCursorPos;
5441         while (p<=SrcLen) do begin
5442           case Src[p] of
5443           ' ',#9: inc(p);
5444           #10,#13:
5445             begin
5446               // after cursor the rest of the line is blank
5447               // check the next line
5448               inc(p);
5449               if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
5450                 inc(p);
5451               if (p>SrcLen) or InEmptyLine(Src,p) then
5452                 InternalCursorAtEmptyLine:=ebTrue
5453               else
5454                 InternalCursorAtEmptyLine:=ebFalse;
5455               break;
5456             end;
5457           else
5458             InternalCursorAtEmptyLine:=ebFalse;
5459             break;
5460           end;
5461         end;
5462       end;
5463     end;
5464     Result:=InternalCursorAtEmptyLine=ebTrue;
5465   end;
5466 
5467   procedure InitStack(out Stack: TBlockStack);
5468   begin
5469     FillByte(Stack{%H-},SizeOf(Stack),0);
5470     Stack.Top:=-1;
5471   end;
5472 
5473   procedure FreeStack(var Stack: TBlockStack);
5474   begin
5475     ReAllocMem(Stack.Stack,0);
5476     Stack.Capacity:=0;
5477     Stack.Top:=-1;
5478   end;
5479 
5480   procedure BeginBlock(var Stack: TBlockStack; Typ: TBlockType;
5481     StartPos: integer);
5482   var
5483     Block: PBlock;
5484   begin
5485     inc(Stack.Top);
5486     if Stack.Top>=Stack.Capacity then begin
5487       if Stack.Capacity=0 then
5488         Stack.Capacity:=16
5489       else
5490         Stack.Capacity:=Stack.Capacity*2;
5491       ReAllocMem(Stack.Stack,SizeOf(TBlock)*Stack.Capacity);
5492     end;
5493     {$IFDEF VerboseCompleteBlock}
5494     DebugLn([GetIndentStr(Stack.Top*2),'BeginBlock ',CleanPosToStr(StartPos),' ',GetAtom]);
5495     {$ENDIF}
5496     Block:=@Stack.Stack[Stack.Top];
5497     Block^.Typ:=Typ;
5498     Block^.StartPos:=StartPos;
5499     Block^.InnerIndent:=-1;
5500     Block^.InnerStartPos:=-1;
5501   end;
5502 
5503   procedure EndBlock(var Stack: TBlockStack);
5504   begin
5505     {$IFDEF VerboseCompleteBlock}
5506     DebugLn([GetIndentStr(Stack.Top*2),'EndBlock ',GetAtom,' ',CleanPosToStr(CurPos.StartPos),', started at ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5507     {$ENDIF}
5508     dec(Stack.Top);
5509   end;
5510 
TopBlockTypenull5511   function TopBlockType(const Stack: TBlockStack): TBlockType;
5512   begin
5513     if Stack.Top>=0 then
5514       Result:=Stack.Stack[Stack.Top].Typ
5515     else
5516       Result:=btNone;
5517   end;
5518 
Replacenull5519   function Replace(NewCode: string; FromPos, ToPos, Indent: integer;
5520     FrontGap, AfterGap: TGapTyp; BeautifyFlags: TBeautifyCodeFlags): boolean;
5521   var
5522     p: LongInt;
5523   begin
5524     Result:=false;
5525     if NewCode='' then exit(true);
5526     // try to avoid changing current line
5527     if (FrontGap=gtEmptyLine) then begin
5528       p:=FromPos;
5529       while (p>1) and (Src[p-1] in [' ',#9]) do dec(p);
5530       if (p=1) or (Src[p] in [#10,#13]) then begin
5531         while (p<=SrcLen) and (Src[p] in [' ',#9]) do inc(p);
5532         if (p>SrcLen) or (Src[p] in [#10,#13]) then begin
5533           // inserting in an empty line
5534           inc(p);
5535           if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
5536             inc(p);
5537           FrontGap:=gtNewLine;
5538           FromPos:=p;
5539           if ToPos<FromPos then ToPos:=FromPos;
5540         end;
5541       end;
5542     end;
5543     // replace trailing spaces
5544     while (ToPos<=SrcLen) and (Src[ToPos] in [' ',#9]) do inc(ToPos);
5545     // use existing semicolon
5546     if (NewCode[length(NewCode)]=';')
5547     and (ToPos<=SrcLen) and (Src[ToPos]=';') then begin
5548       AfterGap:=gtNone;
5549       inc(ToPos);
5550     end;
5551     // use existing "else"
5552     if (NewCode[length(NewCode)]=';') then begin
5553       MoveCursorToCleanPos(ToPos);
5554       ReadNextAtom;
5555       if UpAtomIs('ELSE') then
5556         NewCode:=copy(NewCode,1,length(NewCode)-1);
5557     end;
5558 
5559     // adjust indent of first line
5560     if FrontGap in [gtNone,gtSpace] then begin
5561       BeautifyFlags:=BeautifyFlags+[bcfDoNotIndentFirstLine];
5562       NewCode:=Beauty.GetIndentStr(Indent-GetPosInLine(Src,FromPos))+NewCode;
5563     end;
5564     // beautify
5565     NewCode:=Beauty.BeautifyStatement(
5566                      NewCode,Indent,BeautifyFlags);
5567 
5568     if AfterGap=gtNewLine then begin
5569       // do not reuse existing newline, but always add newline
5570       NewCode:=NewCode+Beauty.LineEnd;
5571       if (ToPos<SrcLen) and (not (Src[ToPos] in [#10,#13])) then
5572         NewCode:=NewCode+Beauty.GetIndentStr(Beauty.GetLineIndent(Src,ToPos));
5573       AfterGap:=gtNone;
5574     end;
5575     {$IFDEF VerboseCompleteBlock}
5576     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)]);
5577     {$ENDIF}
5578     // insert
5579     if not SourceChangeCache.Replace(FrontGap,AfterGap,
5580       FromPos,ToPos,NewCode) then exit;
5581     if not SourceChangeCache.Apply then exit;
5582     Result:=true;
5583   end;
5584 
CompleteStatementsnull5585   function CompleteStatements(var Stack: TBlockStack): Boolean;
5586   var
5587     CursorBlockLvl: Integer; // the stack level of the cursor
5588     LastPos: Integer;
5589     LineStart: boolean; // Atom is first atom of a line in cursor block (not in sub block)
5590     Indent: Integer;
5591     CursorBlockInnerIndent, CursorBlockOuterIndent: LongInt;
5592     CursorBlock: TBlock;
5593     AtomInFrontOfCursor: TAtomPosition;
5594     BehindCursorBlock: Boolean; // atom is behind cursor block
5595     InCursorBlock: Boolean;
5596     NeedCompletion: integer;
5597     InsertPos: LongInt;
5598     NewCode: String;
5599     AfterGap: TGapTyp;
5600     FrontGap: TGapTyp;
5601     BeautifyFlags: TBeautifyCodeFlags;
5602     BehindPos: LongInt;
5603     CursorInEmptyStatement: Boolean;
5604     FromPos: LongInt;
5605     ToPos: LongInt;
5606     WasInCursorBlock: Boolean;
5607 
EndBlockIsOknull5608     function EndBlockIsOk: boolean;
5609     begin
5610       //DebugLn(['EndBlockIsOk ']);
5611       if (NeedCompletion>0) and (CursorBlockLvl>=0)
5612       and (Stack.Top=CursorBlockLvl)
5613       and (Beauty.GetLineIndent(Src,CurPos.StartPos)=CursorBlockOuterIndent) then begin
5614         // cursor block is properly closed => do not complete
5615         {$IFDEF VerboseCompleteBlock}
5616         debugln(['EndBlockIsOk cursor block is properly closed at ',CleanPosToStr(CurPos.StartPos)]);
5617         {$ENDIF}
5618         NeedCompletion:=0;
5619       end;
5620       EndBlock(Stack);
5621       Result:=true;
5622       if (not BehindCursorBlock) and (Stack.Top<CursorBlockLvl) then
5623         BehindCursorBlock:=true;
5624     end;
5625 
CloseBracketsnull5626     function CloseBrackets: boolean;
5627     begin
5628       while TopBlockType(Stack) in [btRoundBracket,btEdgedBracket] do begin
5629         if not EndBlockIsOk then exit(false);
5630       end;
5631       Result:=true;
5632     end;
5633 
InsertPosAtCursornull5634     function InsertPosAtCursor: integer;
5635     begin
5636       Result:=BasicCodeTools.FindLineEndOrCodeInFrontOfPosition(Src,
5637                          CurPos.StartPos,CleanCursorPos,Scanner.NestedComments);
5638     end;
5639 
5640   begin
5641     Result:=false;
5642     MoveCursorToNodeStart(StartNode);
5643     CursorBlockLvl:=-2;
5644     LastPos:=-1;
5645     CursorBlockOuterIndent:=0;
5646     CursorBlockInnerIndent:=0;
5647     Indent:=0;
5648     CursorBlock.StartPos:=0;
5649     BehindCursorBlock:=false;
5650     NeedCompletion:=0;
5651     AtomInFrontOfCursor.StartPos:=0;
5652     CursorInEmptyStatement:=false;
5653     repeat
5654       ReadNextAtom;
5655 
5656       //DebugLn(['ReadStatements Atom=',GetAtom,' TopTyp=',ord(TopBlockType(Stack)),' Top=',Stack.Top]);
5657       if (Stack.Top>=0) and (Stack.Stack[Stack.Top].InnerIndent<0)
5658       and (not PositionsInSameLine(Src,Stack.Stack[Stack.Top].StartPos,CurPos.StartPos))
5659       then begin
5660         // the first atom of this block is on a new line
5661         Stack.Stack[Stack.Top].InnerIndent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
5662         Stack.Stack[Stack.Top].InnerStartPos:=CurPos.StartPos;
5663       end;
5664 
5665       // check if cursor reached
5666       if (CurPos.StartPos>=CleanCursorPos) and (CursorBlockLvl<0) then begin
5667         // reached cursor
5668         CursorBlockLvl:=Stack.Top;
5669         if CursorBlockLvl<0 then begin
5670           // cursor outside blocks or on first atom of first block
5671           {$IFDEF VerboseCompleteBlock}
5672           DebugLn(['ReadStatements no completion: cursor outside blocks or on first atom of first block ',CleanPosToStr(CurPos.StartPos)]);
5673           {$ENDIF}
5674           exit;
5675         end else begin
5676           CursorBlock:=Stack.Stack[CursorBlockLvl];
5677           CursorBlockOuterIndent:=Beauty.GetLineIndent(Src,CursorBlock.StartPos);
5678           CursorBlockInnerIndent:=Stack.Stack[Stack.Top].InnerIndent;
5679           if (CursorBlockInnerIndent<=CursorBlockOuterIndent)
5680           and OnlyIfCursorBlockIndented then begin
5681             // cursor block not indented
5682             {$IFDEF VerboseCompleteBlock}
5683             DebugLn(['ReadStatements no completion: cursor block not indented ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
5684             {$ENDIF}
5685             exit;
5686           end;
5687           AtomInFrontOfCursor:=LastAtoms.GetPriorAtom;
5688           {$IFDEF VerboseCompleteBlock}
5689           DebugLn(['ReadStatements reached cursor: ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' LastAtom=',GetAtom(AtomInFrontOfCursor),' CurAtom=',GetAtom]);
5690           {$ENDIF}
5691           if (CurPos.Flag=cafSemicolon)
5692           and ((AtomInFrontOfCursor.Flag=cafSemicolon)
5693             or (CursorBlock.StartPos=AtomInFrontOfCursor.StartPos))
5694           and (FindNextNonSpace(Src,AtomInFrontOfCursor.EndPos)=CurPos.StartPos)
5695           then begin
5696             // cursor in empty statement
5697             CursorInEmptyStatement:=true;
5698           end;
5699         end;
5700         //DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Indent=',CursorBlockIndent]);
5701       end;
5702 
5703       InCursorBlock:=(CursorBlockLvl>=0) and (CursorBlockLvl=Stack.Top)
5704                      and (not BehindCursorBlock);
5705       WasInCursorBlock:=InCursorBlock;
5706 
5707       // check if end of node
5708       if (CurPos.StartPos>SrcLen) or (CurPos.StartPos>=StartNode.EndPos) then
5709       begin
5710         if InCursorBlock and (NeedCompletion=0) then begin
5711           {$IFDEF VerboseCompleteBlock}
5712           DebugLn(['ReadStatements NeedCompletion: source end found at ',CleanPosToStr(CurPos.StartPos)]);
5713           {$ENDIF}
5714           NeedCompletion:=CleanCursorPos;
5715         end;
5716         break;
5717       end;
5718 
5719       // check if line start vs outer indent
5720       LineStart:=InCursorBlock and (LastPos>0)
5721                  and not PositionsInSameLine(Src,LastPos,CurPos.StartPos);
5722       if LineStart then
5723         Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
5724       if LineStart and (NeedCompletion=0) then begin
5725         // atom is in same block as cursor (not sub block)
5726         // and first atom of a line
5727         // => check indent
5728         //debugln(['CompleteStatements first atom of line in cursor block: ',GetAtom,' Indent=',Indent,' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
5729         if (Indent=CursorBlockOuterIndent) then begin
5730           if (CursorBlockLvl>0)
5731           and (Stack.Stack[CursorBlockLvl-1].InnerIndent=Indent)
5732           and (Stack.Stack[CursorBlockLvl-1].InnerStartPos<CurPos.StartPos)
5733           then begin
5734             { for example:
5735                 Code;
5736                 begin|
5737                 Code;
5738             }
5739             {$IFDEF VerboseCompleteBlock}
5740             DebugLn(['ReadStatements NeedCompletion: between same indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent,' Parent.InnerStartPos=',CleanPosToStr(Stack.Stack[CursorBlockLvl-1].InnerStartPos)]);
5741             {$ENDIF}
5742             NeedCompletion:=InsertPosAtCursor;
5743           end;
5744         end else if (Indent<CursorBlockOuterIndent) then begin
5745           // for example:
5746           //    begin
5747           //    Code;
5748           //  |end;
5749           //DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorAtEmptyLine=',CursorAtEmptyLine,' CursorInEmptyStatement=',CursorInEmptyStatement]);
5750           if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
5751             // for example:
5752             //    begin
5753             //      Code;
5754             //  |end;
5755             {$IFDEF VerboseCompleteBlock}
5756             DebugLn(['ReadStatements NeedCompletion: at out indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
5757             {$ENDIF}
5758             NeedCompletion:=InsertPosAtCursor;
5759           end else if CursorAtEmptyLine or CursorInEmptyStatement
5760           or (FindNextNonSpace(Src,CleanCursorPos)=CurPos.StartPos) then begin
5761             { for example:
5762                   begin
5763                   |
5764                   Code;
5765                 end;
5766 
5767                   begin
5768                   Code;
5769                   |
5770                 end;
5771             }
5772             {$IFDEF VerboseCompleteBlock}
5773             DebugLn(['ReadStatements NeedCompletion: at empty line ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
5774             {$ENDIF}
5775             NeedCompletion:=CleanCursorPos;
5776           end else begin
5777             { It needs completion, but where?
5778               for example:
5779                 begin
5780                   begin|
5781                   Code;
5782                 end;
5783             }
5784           end;
5785         end;
5786       end;
5787 
5788       // check block starts/ends
5789       case CurPos.Flag of
5790       cafEnd:
5791         if (CurPos.EndPos<=SrcLen) and (Src[CurPos.EndPos]='.') then begin
5792           { end. of source found
5793             The parsing started in a begin block, valid cases:
5794 
5795               program a;
5796               begin|
5797               end.
5798 
5799               implementation
5800               begin|
5801               end.
5802           }
5803           if (Stack.Top=0) and (TopBlockType(Stack)=btBegin)
5804           and (StartNode.Desc=ctnBeginBlock)
5805           and ((StartNode.Parent=nil)
5806             or (StartNode.Parent.Desc in AllSourceTypes+[ctnInterface,ctnImplementation]))
5807           then begin
5808             if not EndBlockIsOk then exit; // close main begin
5809           end else begin
5810             // unexpected end of source
5811             {$IFDEF VerboseCompleteBlock}
5812             DebugLn(['ReadStatements unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
5813             {$ENDIF}
5814             if InCursorBlock and (NeedCompletion=0) then begin
5815               {$IFDEF VerboseCompleteBlock}
5816               DebugLn(['ReadStatements NeedCompletion: unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
5817               {$ENDIF}
5818               NeedCompletion:=CleanCursorPos;
5819             end;
5820           end;
5821           break;
5822         end else begin
5823           case TopBlockType(Stack) of
5824           btCaseOf,btCaseElse:
5825             begin
5826               if not EndBlockIsOk then exit; // close btCaseOf,btCaseElse
5827               if not EndBlockIsOk then exit; // close btCase
5828             end;
5829           btBegin,btFinally,btExcept,btCase:
5830             if not EndBlockIsOk then exit;
5831           btCaseColon,btRepeat:
5832             begin
5833               // missing semicolon or until
5834               DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
5835               DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5836               if InCursorBlock and (NeedCompletion=0) then begin
5837                 {$IFDEF VerboseCompleteBlock}
5838                 DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5839                 {$ENDIF}
5840                 NeedCompletion:=CleanCursorPos;
5841               end;
5842               break;
5843             end;
5844           btTry:
5845             begin
5846               // missing finally/except
5847               DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
5848               DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5849               if InCursorBlock and (NeedCompletion=0) then begin
5850                 {$IFDEF VerboseCompleteBlock}
5851                 DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5852                 {$ENDIF}
5853                 NeedCompletion:=CleanCursorPos;
5854               end;
5855               break;
5856             end;
5857           btAsm:
5858             if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]<>'@') then begin
5859               if not EndBlockIsOk then exit;
5860             end;
5861           else
5862             // missing begin
5863             exit;
5864           end;
5865         end;
5866       cafEdgedBracketOpen:
5867         BeginBlock(Stack,btEdgedBracket,CurPos.StartPos);
5868       cafEdgedBracketClose:
5869         if TopBlockType(Stack)=btEdgedBracket then begin
5870           if not EndBlockIsOk then exit;
5871         end else begin
5872           // missing [
5873           exit;
5874         end;
5875       cafRoundBracketOpen:
5876         BeginBlock(Stack,btRoundBracket,CurPos.StartPos);
5877       cafRoundBracketClose:
5878         if TopBlockType(Stack)=btRoundBracket then begin
5879           if not EndBlockIsOk then exit;
5880         end else begin
5881           // missing (
5882           exit;
5883         end;
5884       cafColon:
5885         if TopBlockType(Stack)=btCaseOf then
5886           BeginBlock(Stack,btCaseColon,CurPos.StartPos);
5887       cafSemicolon:
5888         while TopBlockType(Stack)
5889         in [btCaseColon,btIf,btIfElse,btRoundBracket,btEdgedBracket] do begin
5890           if not EndBlockIsOk then exit;
5891         end;
5892       cafWord:
5893         if TopBlockType(Stack)<>btAsm then begin
5894           if UpAtomIs('BEGIN') then
5895             BeginBlock(Stack,btBegin,CurPos.StartPos)
5896           else if UpAtomIs('TRY') then
5897             BeginBlock(Stack,btTry,CurPos.StartPos)
5898           else if UpAtomIs('FINALLY') then begin
5899             if TopBlockType(Stack)=btTry then
5900               if not EndBlockIsOk then exit;
5901             BeginBlock(Stack,btFinally,CurPos.StartPos)
5902           end else if UpAtomIs('EXCEPT') then begin
5903             if TopBlockType(Stack)=btTry then
5904               if not EndBlockIsOk then exit;
5905             BeginBlock(Stack,btExcept,CurPos.StartPos)
5906           end else if UpAtomIs('REPEAT') then
5907             BeginBlock(Stack,btRepeat,CurPos.StartPos)
5908           else if UpAtomIs('UNTIL') then begin
5909             if TopBlockType(Stack)=btRepeat then begin
5910               if not EndBlockIsOk then exit;
5911             end else begin
5912               // until without repeat
5913               DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock,' Block=',ord(TopBlockType(Stack))]);
5914               DebugLn(['ReadStatements unexpected until at ',CleanPosToStr(CurPos.StartPos)]);
5915               exit;
5916             end;
5917           end else if UpAtomIs('ASM') then begin
5918             BeginBlock(Stack,btAsm,CurPos.StartPos);
5919           end else if UpAtomIs('IF') then begin
5920             BeginBlock(Stack,btIf,CurPos.StartPos);
5921           end else if UpAtomIs('THEN') then begin
5922             CloseBrackets;
5923             if TopBlockType(Stack)=btIf then begin
5924               Stack.Stack[Stack.Top].InnerIndent:=-1;
5925               Stack.Stack[Stack.Top].InnerStartPos:=-1;
5926             end;
5927           end else if UpAtomIs('CASE') then begin
5928             BeginBlock(Stack,btCase,CurPos.StartPos)
5929           end else if UpAtomIs('OF') then begin
5930             CloseBrackets;
5931             if TopBlockType(Stack)=btCase then
5932               BeginBlock(Stack,btCaseOf,CurPos.StartPos);
5933           end else if UpAtomIs('ELSE') then begin
5934             CloseBrackets;
5935             case TopBlockType(Stack) of
5936             btIf:
5937               begin
5938                 if not EndBlockIsOk then exit;
5939                 BeginBlock(Stack,btIfElse,CurPos.StartPos);
5940               end;
5941             btCaseOf:
5942               begin
5943                 if not EndBlockIsOk then exit;
5944                 BeginBlock(Stack,btCaseElse,CurPos.StartPos);
5945               end;
5946             btBegin:
5947               begin
5948                 // missing end
5949                 if InCursorBlock and (NeedCompletion=0) then begin
5950                   {$IFDEF VerboseCompleteBlock}
5951                   DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing end. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5952                   {$ENDIF}
5953                   NeedCompletion:=InsertPosAtCursor;
5954                 end;
5955                 break;
5956               end;
5957             btCaseColon,btRepeat:
5958               begin
5959                 // missing semicolon
5960                 if InCursorBlock and (NeedCompletion=0) then begin
5961                   {$IFDEF VerboseCompleteBlock}
5962                   DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
5963                   {$ENDIF}
5964                   NeedCompletion:=InsertPosAtCursor;
5965                 end;
5966                 break;
5967               end;
5968             end;
5969           end else if UpAtomIs('PROCEDURE') or UpAtomIs('FUNCTION')
5970           or UpAtomIs('CONSTRUCTOR') or UpAtomIs('DESTRUCTOR')
5971           or UpAtomIs('VAR') or UpAtomIs('TYPE') or UpAtomIs('CONST')
5972           or UpAtomIs('RESOURCESTRING') or UpAtomIs('LABEL') or UpAtomIs('CLASS')
5973           or UpAtomIs('INITIALIZATION') or UpAtomIs('FINALIZATION')
5974           then begin
5975             // unexpected keyword => block not closed
5976             if InCursorBlock and (NeedCompletion=0) then begin
5977               {$IFDEF VerboseCompleteBlock}
5978               DebugLn(['ReadStatements NeedCompletion: unexpected keyword ',GetAtom,' at ',CleanPosToStr(CurPos.StartPos)]);
5979               {$ENDIF}
5980               NeedCompletion:=CleanCursorPos;
5981             end;
5982             break;
5983           end;
5984         end;
5985       end;
5986 
5987       // check if line start
5988       if LineStart and WasInCursorBlock and (not BehindCursorBlock) then begin
5989         // atom is first atom of a line
5990         // and atom is in same block as cursor (not sub block)
5991         // (maybe the atom started a new sub block, but it did not close it)
5992         // => check indent
5993         //debugln(['CompleteStatements ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
5994         if (Indent<CursorBlockInnerIndent) and (NeedCompletion=0) then begin
5995           if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
5996             // for example:
5997             //  begin
5998             //    Code;
5999             //    |
6000             //    Code;
6001             //  Code;
6002             //DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockIndent=',CursorBlockIndent]);
6003             {$IFDEF VerboseCompleteBlock}
6004             DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
6005             {$ENDIF}
6006             NeedCompletion:=InsertPosAtCursor;
6007           end else begin
6008             // for example:
6009             // begin
6010             // |
6011             // Code;
6012             {$IFDEF VerboseCompleteBlock}
6013             DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
6014             {$ENDIF}
6015             NeedCompletion:=CleanCursorPos;
6016             // Note: if the end is coming later, NeedCompletion is disabled
6017           end;
6018         end;
6019       end;
6020 
6021       LastPos:=CurPos.StartPos;
6022     until Stack.Top<0;
6023 
6024     {$IFDEF VerboseCompleteBlock}
6025     DebugLn(['ReadStatements END Stack.Top=',Stack.Top,' CursorBlockLvl=',CursorBlockLvl,' BehindCursorBlock=',BehindCursorBlock]);
6026     {$ENDIF}
6027 
6028     if Stack.Top<0 then begin
6029       // all blocks closed
6030       {$IFDEF VerboseCompleteBlock}
6031       if NeedCompletion>0 then
6032         DebugLn(['ReadStatements all blocks closed: no completion needed']);
6033       {$ENDIF}
6034       NeedCompletion:=0;
6035     end;
6036 
6037     if (NeedCompletion>0) then begin
6038       InsertPos:=NeedCompletion;
6039       while (InsertPos>CleanCursorPos) and (IsSpaceChar[Src[InsertPos-1]]) do
6040         dec(InsertPos);
6041       Indent:=CursorBlockOuterIndent;
6042 
6043       // check code behind
6044       BehindPos:=FindNextNonSpace(Src,InsertPos);
6045       if BehindPos<=SrcLen then begin
6046         if (not CursorInEmptyStatement)
6047         and PositionsInSameLine(Src,InsertPos,BehindPos) then begin
6048           // target line not empty
6049           {$IFDEF VerboseCompleteBlock}
6050           DebugLn(['CompleteStatements target line not empty => skip']);
6051           {$ENDIF}
6052           exit;
6053         end;
6054         if (Beauty.GetLineIndent(Src,BehindPos)>Indent) then begin
6055           // code behind is more indented
6056           // for example
6057           //   repeat
6058           //   |
6059           //     DoSomething;
6060           debugln(['CompleteStatements BehindPos ',dbgstr(copy(Src,BehindPos-8,8)),'|',dbgstr(copy(Src,BehindPos,8))]);
6061           {$IFDEF VerboseCompleteBlock}
6062           DebugLn(['CompleteStatements code behind is indented more (Behind=',Beauty.GetLineIndent(Src,BehindPos),' > Indent=',Indent,') => skip']);
6063           {$ENDIF}
6064           exit;
6065         end;
6066       end;
6067 
6068       NewCode:=';';
6069       FrontGap:=gtEmptyLine;
6070       AfterGap:=gtNewLine;
6071       FromPos:=InsertPos;
6072       ToPos:=InsertPos;
6073       BeautifyFlags:=[bcfIndentExistingLineBreaks];
6074       if CursorInEmptyStatement and (BehindPos<=SrcLen) then begin
6075         // replace the empty statement
6076         FrontGap:=gtNewLine;
6077         ToPos:=BehindPos;
6078       end;
6079       case CursorBlock.Typ of
6080       btBegin,btFinally,btExcept,btAsm,btCaseOf,btCaseElse:
6081         NewCode:='end'+NewCode;
6082       btRepeat:
6083         NewCode:='until '+NewCode;
6084       btTry:
6085         NewCode:='finally'+SourceChangeCache.BeautifyCodeOptions.LineEnd
6086            +'end'+NewCode;
6087       btCaseColon:
6088         begin
6089           FrontGap:=gtNone;
6090           AfterGap:=gtNone;
6091         end;
6092       else
6093         exit;
6094       end;
6095       if (CursorBlockLvl=0) and (AfterGap=gtNewLine) then begin
6096         // top level => insert empty lines between top level structures
6097         AfterGap:=gtEmptyLine;
6098       end;
6099       if not Replace(NewCode,FromPos,ToPos,Indent,FrontGap,AfterGap,
6100         BeautifyFlags) then exit;
6101     end;
6102     Result:=true;
6103   end;
6104 
CompleteClassSectionnull6105   function CompleteClassSection: Boolean;
6106   {  type
6107        TMyClass = class
6108          |
6109   }
6110   var
6111     LastIndent: LongInt;
6112     Indent: LongInt;
6113     InsertPos: LongInt;
6114     NeedCompletion: Integer;
6115   begin
6116     Result:=false;
6117     if CleanCursorPos<StartNode.StartPos then exit;
6118     LastIndent:=Beauty.GetLineIndent(Src,StartNode.Parent.StartPos);
6119     MoveCursorToNodeStart(StartNode);
6120     //debugln(['CompleteClassSection ',dbgstr(copy(Src,StartNode.StartPos-10,10)),'|',dbgstr(copy(Src,StartNode.StartPos,10))]);
6121     Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
6122     if Indent<LastIndent then
6123       LastIndent:=Indent;
6124     ReadNextAtom;
6125     NeedCompletion:=0;
6126     if (CurPos.StartPos>SrcLen) then begin
6127       { For example:
6128           TMyClass = class
6129           <EOF>
6130       }
6131       NeedCompletion:=CleanCursorPos;
6132     end else if CurPos.Flag=cafWord then begin
6133       if AtomIsIdentifier then begin
6134         ReadNextAtom;
6135         if CurPos.Flag=cafEqual then begin
6136           { For example:
6137               TMyClass = class
6138 
6139               TIdentifier =
6140           }
6141           NeedCompletion:=CleanCursorPos;
6142         end else
6143           exit(true);
6144       end else begin
6145         Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
6146         if Indent<LastIndent then begin
6147           { For example:
6148                 TMyClass = class
6149 
6150               type
6151           }
6152           NeedCompletion:=CleanCursorPos;
6153         end;
6154       end;
6155     end else
6156       exit(true);
6157     //debugln(['CompleteClassSection NeedCompletion=',NeedCompletion]);
6158     if NeedCompletion>0 then begin
6159       InsertPos:=NeedCompletion;
6160       Result:=Replace('end;',InsertPos,InsertPos,LastIndent,
6161         gtNewLine,gtEmptyLine,
6162         [bcfIndentExistingLineBreaks]);
6163     end else
6164       Result:=true;
6165   end;
6166 
CompleteClassInterfacenull6167   function CompleteClassInterface: Boolean;
6168   {  type
6169        TMyClass = interface
6170          |
6171   }
6172   var
6173     LastIndent: LongInt;
6174     Indent: LongInt;
6175     InsertPos: LongInt;
6176   begin
6177     Result:=false;
6178     if CleanCursorPos<StartNode.StartPos then exit;
6179     LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
6180     MoveCursorToNodeStart(StartNode);
6181     ReadNextAtom;
6182     if CleanCursorPos<CurPos.EndPos then exit(true);
6183     ReadNextAtom;
6184     if CurPos.Flag=cafEnd then exit(true);
6185     if CleanCursorPos<=CurPos.StartPos then begin
6186       Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
6187       InsertPos:=CleanCursorPos;
6188       if Indent<LastIndent then begin
6189         if not Replace('end;',InsertPos,InsertPos,LastIndent,
6190           gtNewLine,gtEmptyLine,
6191           [bcfIndentExistingLineBreaks])
6192         then
6193           exit;
6194       end;
6195     end;
6196     Result:=true;
6197   end;
6198 
CompleteRecordnull6199   function CompleteRecord: Boolean;
6200   {  type
6201        TMyClass = record
6202          |
6203   }
6204   var
6205     LastIndent: LongInt;
6206     Indent: LongInt;
6207     InsertPos: LongInt;
6208   begin
6209     Result:=false;
6210     if CleanCursorPos<StartNode.StartPos then exit;
6211     LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
6212     MoveCursorToNodeStart(StartNode);
6213     ReadNextAtom; // record
6214     if CleanCursorPos<CurPos.EndPos then exit(true);
6215     ReadNextAtom;
6216     if CurPos.Flag=cafEnd then exit(true);
6217     if CleanCursorPos<=CurPos.StartPos then begin
6218       Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
6219       InsertPos:=CleanCursorPos;
6220       if Indent<=LastIndent then begin
6221         if not Replace('end;',InsertPos,InsertPos,LastIndent,
6222           gtNewLine,gtEmptyLine,
6223           [bcfIndentExistingLineBreaks])
6224         then
6225           exit;
6226       end;
6227     end;
6228     Result:=true;
6229   end;
6230 
6231 var
6232   Stack: TBlockStack;
6233   CommentStart, CommentEnd: integer;
6234 begin
6235   Result:=false;
6236   NewPos:=CursorPos;
6237   NewTopLine:=-1;
6238   BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
6239                           [btSetIgnoreErrorPos]);
6240   StartNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
6241   if CleanPosIsInComment(CleanCursorPos,StartNode.StartPos,CommentStart,CommentEnd) then begin
6242     {$IFDEF VerboseCompleteBlock}
6243     debugln(['TStandardCodeTool.CompleteBlock cursor (',CursorPos.Y,',',CursorPos.X,') cleanpos=[',CleanPosToStr(CleanCursorPos),'] in comment Start=[',CleanPosToStr(CommentStart),'] End=[',CleanPosToStr(CommentEnd),']']);
6244     {$ENDIF}
6245     exit;
6246   end;
6247 
6248   InternalCursorAtEmptyLine:=ebNone;
6249   SourceChangeCache.MainScanner:=Scanner;
6250   Beauty:=SourceChangeCache.BeautifyCodeOptions;
6251   InitStack(Stack);
6252   try
6253     {$IFDEF VerboseCompleteBlock}
6254     DebugLn(['TStandardCodeTool.CompleteBlock ',StartNode.DescAsString]);
6255     {$ENDIF}
6256 
6257     if StartNode.Desc in AllPascalStatements then begin
6258       while (StartNode.Parent<>nil)
6259       and (StartNode.Parent.Desc in AllPascalStatements) do
6260         StartNode:=StartNode.Parent;
6261       if not CompleteStatements(Stack) then exit;
6262     end
6263     else if (StartNode.Desc in AllClassSections)
6264     or ((StartNode.Desc in AllClassSubSections) and (StartNode.Parent.Desc in AllClassSections))
6265     then begin
6266       if not CompleteClassSection then exit;
6267     end
6268     else if StartNode.Desc in AllClassInterfaces then begin
6269       if not CompleteClassInterface then exit;
6270     end
6271     else if StartNode.Desc=ctnRecordType then begin
6272       if not CompleteRecord then exit;
6273     end;
6274   finally
6275     FreeStack(Stack);
6276   end;
6277 
6278   Result:=true;
6279 end;
6280 
6281 {$IFDEF GuessMisplacedIfdef}
GuessMisplacedIfdefEndifnull6282 function TStandardCodeTool.GuessMisplacedIfdefEndif(
6283   const CursorPos: TCodeXYPosition;
6284   out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
6285 var
6286   StartCursorPos, EndCursorPos: integer;
6287   StartCode, EndCode: Pointer;
6288 begin
6289   Result:=false;
6290   try
6291     BeginParsing(lsrEnd);
6292   except
6293     // ignore scanner and parser errors
6294     on e: ELinkScannerError do ;
6295     on e: ECodeToolError do ;
6296   end;
6297   if Scanner<>nil then begin
6298     CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,StartCursorPos);
6299     StartCode:=CursorPos.Code;
6300     Result:=Scanner.GuessMisplacedIfdefEndif(StartCursorPos,StartCode,
6301                                              EndCursorPos,EndCode);
6302     if Result then begin
6303       NewPos.Code:=TCodeBuffer(EndCode);
6304       NewPos.Code.AbsoluteToLineCol(EndCursorPos,NewPos.Y,NewPos.X);
6305       if JumpSingleLinePos>0 then begin
6306         NewTopLine:=NewPos.Y-(VisibleEditorLines*JumpSingleLinePos div 100);
6307         if NewTopLine<1 then NewTopLine:=1;
6308       end else
6309         NewTopLine:=NewPos.Y;
6310     end;
6311   end;
6312 end;
6313 {$ENDIF}
6314 
TStandardCodeTool.FindEnclosingIncludeDirectivenull6315 function TStandardCodeTool.FindEnclosingIncludeDirective(
6316   const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
6317   NewTopLine: integer): boolean;
6318 var
6319   CleanCursorPos, LinkIndex, NewCleanPos: integer;
6320 begin
6321   Result:=false;
6322   NewPos:=CleanCodeXYPosition;
6323   NewTopLine:=-1;
6324   try
6325     BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
6326                             [btSetIgnoreErrorPos]);
6327     LinkIndex:=Scanner.LinkIndexAtCleanPos(CleanCursorPos);
6328     LinkIndex:=Scanner.FindParentLink(LinkIndex);
6329     if LinkIndex<0 then
6330       // this is no include file
6331       exit;
6332     NewPos.Code:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
6333     // calculate the directive end bracket
6334     NewCleanPos:=Scanner.Links[LinkIndex].CleanedPos+Scanner.LinkSize(LinkIndex)-1;
6335     Result:=CleanPosToCaretAndTopLine(NewCleanPos,NewPos,NewTopLine);
6336   finally
6337     ClearIgnoreErrorAfter;
6338   end;
6339 end;
6340 
FindModeDirectivenull6341 function TStandardCodeTool.FindModeDirective(DoBuildTree: boolean;
6342   out ACleanPos: integer): boolean;
6343 var
6344   ParamPos: Integer;
6345 begin
6346   Result:=false;
6347   ACleanPos:=0;
6348   if DoBuildTree then BuildTree(lsrMainUsesSectionStart);
6349   ACleanPos:=FindNextCompilerDirectiveWithName(Src,1,'Mode',
6350                                                Scanner.NestedComments,ParamPos);
6351   if ParamPos=0 then ;
6352   Result:=(ACleanPos>0) and (ACleanPos<=SrcLen);
6353 end;
6354 
FindResourceDirectivenull6355 function TStandardCodeTool.FindResourceDirective(DoBuildTree: boolean;
6356   var ACleanPos: integer; const Filename: string): boolean;
6357 var
6358   ParamPos: Integer;
6359   FilenameStartPos: Integer;
6360   FilenameEndPos: LongInt;
6361 begin
6362   Result:=false;
6363   if DoBuildTree then BuildTree(lsrEnd);
6364   ACleanPos:=1;
6365   repeat
6366     ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
6367       Scanner.NestedComments,ParamPos);
6368     if ParamPos=0 then ;
6369     if (ACleanPos<1) or (ACleanPos>SrcLen) then
6370       exit(false);
6371     if Filename='' then begin
6372       // searching any filename -> found
6373       exit(true);
6374     end;
6375     FilenameStartPos:=ACleanPos+length('{$R ');
6376     FilenameEndPos:=FilenameStartPos;
6377     while (FilenameEndPos<=SrcLen) and (Src[FilenameEndPos]<>'}') do
6378       inc(FilenameEndPos);
6379     if CompareText(PChar(Pointer(Filename)),length(Filename),
6380                    @Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
6381                    true,false)=0
6382     then begin
6383       // filename found
6384       exit(true);
6385     end;
6386     ACleanPos:=FilenameEndPos+1;
6387   until ACleanPos>SrcLen;
6388 end;
6389 
FindResourceDirectivenull6390 function TStandardCodeTool.FindResourceDirective(
6391   const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition;
6392   out NewTopLine: integer; const Filename: string): boolean;
6393 var
6394   CleanCursorPos: integer;
6395 begin
6396   Result:=false;
6397   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
6398   if not FindResourceDirective(false,CleanCursorPos,Filename) then begin
6399     //DebugLn('TStandardCodeTool.FindResourceDirective resource directive not found');
6400     exit;
6401   end;
6402   Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
6403 end;
6404 
TStandardCodeTool.AddResourceDirectivenull6405 function TStandardCodeTool.AddResourceDirective(const Filename: string;
6406   SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
6407 var
6408   ANode: TCodeTreeNode;
6409   Indent: LongInt;
6410   InsertPos: Integer;
6411   AddSrc: String;
6412   Beauty: TBeautifyCodeOptions;
6413 begin
6414   Result:=false;
6415   BuildTree(lsrEnd);
6416   Beauty:=SourceChangeCache.BeautifyCodeOptions;
6417   // find an insert position
6418   ANode:=FindImplementationNode;
6419   if ANode<>nil then begin
6420     Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
6421     InsertPos:=ANode.StartPos+length('implementation');
6422   end else begin
6423     ANode:=FindMainBeginEndNode;
6424     if ANode<>nil then begin
6425       Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
6426       InsertPos:=ANode.StartPos;
6427     end else begin
6428       ANode:=FindMainUsesNode;
6429       if ANode<>nil then begin
6430         Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
6431         InsertPos:=ANode.StartPos;
6432       end else begin
6433         Indent:=0;
6434         InsertPos:=1;
6435       end;
6436     end;
6437   end;
6438 
6439   // insert directive
6440   SourceChangeCache.MainScanner:=Scanner;
6441   if NewSrc<>'' then
6442     AddSrc:=NewSrc
6443   else
6444     AddSrc:=Beauty.GetIndentStr(Indent)+'{$R '+Filename+'}';
6445   if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
6446     AddSrc) then exit;
6447   if not SourceChangeCache.Apply then exit;
6448 
6449   Result:=true;
6450 end;
6451 
FindIncludeDirectivenull6452 function TStandardCodeTool.FindIncludeDirective(DoBuildTree: boolean;
6453   var ACleanPos: integer; const Filename: string): boolean;
6454 var
6455   FilenameStartPos: Integer;
6456   FilenameEndPos: LongInt;
6457   CommentStart: integer;
6458   CommentEnd: integer;
6459 begin
6460   Result:=false;
6461   if DoBuildTree then BuildTree(lsrEnd);
6462   ACleanPos:=1;
6463   repeat
6464     ACleanPos:=FindNextIncludeDirective(Src,ACleanPos,Scanner.NestedComments,
6465                        FilenameStartPos,FilenameEndPos,CommentStart,CommentEnd);
6466     if (ACleanPos<1) or (ACleanPos>SrcLen) then
6467       exit(false);
6468     if Filename='' then begin
6469       // searching any filename -> found
6470       exit(true);
6471     end;
6472     if CompareText(PChar(Pointer(Filename)),length(Filename),
6473                    @Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
6474                    true,false)=0
6475     then begin
6476       // filename found
6477       exit(true);
6478     end;
6479     ACleanPos:=FilenameEndPos+1;
6480   until ACleanPos>SrcLen;
6481 end;
6482 
FindIncludeDirectivenull6483 function TStandardCodeTool.FindIncludeDirective(
6484   const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
6485   NewTopLine: integer; const Filename: string): boolean;
6486 var
6487   CleanCursorPos: integer;
6488 begin
6489   Result:=false;
6490   BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
6491   if not FindIncludeDirective(false,CleanCursorPos,Filename) then begin
6492     //DebugLn('TStandardCodeTool.FindIncludeDirective resource directive not found');
6493     exit;
6494   end;
6495   Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
6496 end;
6497 
TStandardCodeTool.AddIncludeDirectiveForInitnull6498 function TStandardCodeTool.AddIncludeDirectiveForInit(const Filename: string;
6499   SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
6500 var
6501   ANode: TCodeTreeNode;
6502   Indent: LongInt;
6503   InsertPos: Integer;
6504   AddSrc: String;
6505   Beauty: TBeautifyCodeOptions;
6506 begin
6507   Result:=false;
6508   BuildTree(lsrEnd);
6509   Beauty:=SourceChangeCache.BeautifyCodeOptions;
6510   // find an insert position
6511   ANode:=FindInitializationNode;
6512   if ANode<>nil then begin
6513     Indent:=Beauty.GetLineIndent(Src,ANode.StartPos)+Beauty.Indent;
6514     InsertPos:=ANode.StartPos+length('initialization');
6515   end else begin
6516     ANode:=FindMainBeginEndNode;
6517     if ANode<>nil then begin
6518       MoveCursorToNodeStart(ANode);
6519       ReadNextAtom;
6520       //debugln(['TStandardCodeTool.AddIncludeDirective ',GetAtom]);
6521       Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos)+Beauty.Indent;
6522       InsertPos:=CurPos.EndPos;
6523     end else begin
6524       debugln(['TStandardCodeTool.AddIncludeDirective ToDo: add initialization / begin..end']);
6525       exit;
6526     end;
6527   end;
6528 
6529   // insert directive
6530   SourceChangeCache.MainScanner:=Scanner;
6531   if NewSrc<>'' then
6532     AddSrc:=NewSrc
6533   else
6534     AddSrc:=Beauty.GetIndentStr(Indent)+'{$I '+Filename+'}';
6535   if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
6536     AddSrc) then exit;
6537   if not SourceChangeCache.Apply then exit;
6538 
6539   Result:=true;
6540 end;
6541 
AddUnitWarnDirectivenull6542 function TStandardCodeTool.AddUnitWarnDirective(WarnID, Comment: string;
6543   TurnOn: boolean; SourceChangeCache: TSourceChangeCache): boolean;
6544 const
6545   DirectiveFlagValue: array[boolean] of string = ('off','on');
6546 var
6547   ACleanPos, DirEndPos, InsertStartPos, MaxPos: Integer;
6548   Node: TCodeTreeNode;
6549   p, IDStartPos, IDEndPos, ParamPos: PChar;
6550   NewCode: String;
6551 begin
6552   Result:=false;
6553   if WarnID='' then
6554     raise Exception.Create('TStandardCodeTool.AddUnitWarnDirective missing WarnID');
6555   InsertStartPos:=0;
6556   BuildTree(lsrMainUsesSectionStart);
6557   SourceChangeCache.MainScanner:=Scanner;
6558 
6559   // fix comment
6560   if Comment<>'' then begin
6561     for ACleanPos:=1 to length(Comment) do
6562       if Comment[ACleanPos] in [#0..#8,#11,#12,#14..#31,'{','}'] then
6563         Comment[ACleanPos]:='?';
6564     if not (Comment[1] in [' ',#9,#10,#13]) then Comment:=' '+Comment;
6565   end;
6566 
6567   // insert in front of first node after source name
6568   Node:=Tree.Root;
6569   MaxPos:=Node.StartPos;
6570   if Node.Desc in AllSourceTypes then
6571     Node:=Node.Next;
6572   if (Node<>nil) and (Node.Desc=ctnSrcName) then begin
6573     MaxPos:=Node.EndPos;
6574     Node:=Node.NextSkipChilds;
6575   end;
6576   if Node<>nil then
6577     MaxPos:=Node.StartPos;
6578   MaxPos:=FindLineEndOrCodeAfterPosition(MaxPos,true,true);
6579 
6580   // find existing directive for replacement
6581   ACleanPos:=1;
6582   repeat
6583     ACleanPos:=FindNextCompilerDirective(Src,ACleanPos,Scanner.NestedComments);
6584     if (ACleanPos<1) or (ACleanPos>MaxPos) then
6585       break;
6586     DirEndPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments)+1;
6587     p:=@Src[ACleanPos+2];
6588     if CompareIdentifiers(p,'warn')=0 then begin
6589       IDStartPos:=p+4;
6590       while IDStartPos^ in [' ',#9,#10,#13] do
6591         inc(IDStartPos);
6592       IDEndPos:=IDStartPos;
6593       while IDEndPos^ in ['0'..'9','A'..'Z','a'..'z','_'] do
6594         inc(IDEndPos);
6595       if CompareText(PChar(Pointer(WarnID)),length(WarnID),
6596                      IDStartPos,IDEndPos-IDStartPos,false)=0
6597       then begin
6598         // warn directive found
6599         p:=IDEndPos;
6600         while p^ in [' ',#9,#10,#13] do
6601           inc(p);
6602         ParamPos:=p;
6603         while p^ in ['+','-','a'..'z','A'..'Z'] do
6604           inc(p);
6605         if not SourceChangeCache.Replace(gtSpace,gtNone,
6606           ParamPos-PChar(Src)+1,p-PChar(Src)+1,DirectiveFlagValue[TurnOn])
6607         then
6608           exit;
6609         Result:=SourceChangeCache.Apply;
6610         exit;
6611       end;
6612     end else if (CompareIdentifiers(p,'i')=0) or (CompareIdentifiers(p,'include')=0)
6613     then begin
6614       // insert before include file
6615       if MaxPos>ACleanPos then MaxPos:=ACleanPos;
6616       break;
6617     end;
6618     ACleanPos:=DirEndPos;
6619   until ACleanPos>MaxPos;
6620 
6621   // there was no such directive yet -> find nice insert pos
6622   InsertStartPos:=FindLineEndOrCodeInFrontOfPosition(MaxPos,true,true);
6623   NewCode:='{$WARN '+WarnID+' '+DirectiveFlagValue[TurnOn]+Comment+'}';
6624   if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
6625     InsertStartPos,InsertStartPos,NewCode)
6626   then
6627     exit;
6628   Result:=SourceChangeCache.Apply;
6629 end;
6630 
TStandardCodeTool.FixIncludeFilenamesnull6631 function TStandardCodeTool.FixIncludeFilenames(Code: TCodeBuffer;
6632   SourceChangeCache: TSourceChangeCache;
6633   out FoundIncludeFiles: TStrings;
6634   var MissingIncludeFilesCodeXYPos: TFPList): boolean;
6635 var
6636   ASource: String;
6637 
6638   {procedure WriteMissingIncludeFilesCodeXYPos;
6639   var
6640     CodePos: PCodeXYPosition;
6641     i: Integer;
6642   begin
6643     if MissingIncludeFilesCodeXYPos<>nil then begin
6644       for i:=0 to MissingIncludeFilesCodeXYPos.Count-1 do begin
6645         CodePos:=PCodeXYPosition(MissingIncludeFilesCodeXYPos[i]);
6646         DebugLn('TStandardCodeTool.FixMissingUnits ',dbgs(CodePos));
6647         DebugLn('TStandardCodeTool.FixMissingUnits ',CodePos^.Code.Filename);
6648         debugln(CodePos^.Code.Filename
6649              +'('+IntToStr(CodePos^.y)+','+IntToStr(CodePos^.x)+')'
6650              +' missing include file');
6651       end;
6652     end;
6653   end;}
6654 
6655   procedure Add(FilenameSrcPos: integer; const AFilename: string; Found: boolean);
6656   var
6657     NewFilename: String;
6658     p: PCodeXYPosition;
6659   begin
6660     if Found then begin
6661       if FoundIncludeFiles=nil then
6662         FoundIncludeFiles:=TStringListUTF8Fast.Create;
6663       NewFilename:=TrimFilename(AFilename);
6664       if FoundIncludeFiles.IndexOf(NewFilename)<0 then
6665         FoundIncludeFiles.Add(NewFilename);
6666     end else begin
6667       if MissingIncludeFilesCodeXYPos=nil then
6668         MissingIncludeFilesCodeXYPos:=TFPList.Create;
6669       New(p);
6670       p^.Code:=Code;
6671       Code.AbsoluteToLineCol(FilenameSrcPos,p^.y,p^.x);
6672       MissingIncludeFilesCodeXYPos.Add(p);
6673       ///DebugLn('TStandardCodeTool.FixIncludeFilenames.Add "',p^.Code.Filename,'" ',dbgs(p),' X=',dbgs(p^.X),' Y=',dbgs(p^.Y));
6674       //WriteMissingIncludeFilesCodeXYPos;
6675     end;
6676   end;
6677 
SearchIncludeFilenamenull6678   function SearchIncludeFilename(FilenameSrcPos: integer; const AFilename: string): string;
6679   var
6680     AFilePath: String;
6681     BaseDir: String;
6682     CurFilename: String;
6683     IncludePath: String;
6684     PathDivider: String;
6685     ACodeBuf: TCodeBuffer;
6686   begin
6687     Result:=TrimFilename(AFilename);
6688     if FilenameIsAbsolute(Result) then begin
6689       Result:=DirectoryCache.Pool.FindDiskFilename(Result,true);
6690       Add(FilenameSrcPos,Result,FileExistsCached(Result));
6691       //DebugLn('SearchIncludeFilename AbsoluteFilename="',Result,'"');
6692     end else begin
6693       BaseDir:=ExtractFilePath(MainFilename);
6694       //DebugLn('SearchIncludeFilename BaseDir="',BaseDir,'"');
6695       if FilenameIsAbsolute(BaseDir) then begin
6696         // unit has normal path -> not virtual
6697         AFilePath:=ExtractFilePath(Result);
6698         if AFilePath<>'' then begin
6699           // search relative to unit
6700           //debugln(['SearchIncludeFilename BaseDir+Result=',BaseDir+Result]);
6701           CurFilename:=DirectoryCache.Pool.FindDiskFilename(BaseDir+Result,true);
6702           //debugln(['SearchIncludeFilename DiskFilename=',CurFilename]);
6703           Result:=CreateRelativePath(CurFilename,BaseDir);
6704           //debugln(['SearchIncludeFilename RelativeDiskFilename=',Result]);
6705           if FileExistsCached(CurFilename) then
6706             Add(FilenameSrcPos,CurFilename,true)
6707           else
6708             Add(FilenameSrcPos,Result,false);
6709           //DebugLn('SearchIncludeFilename relative filename="',CurFilename,'"');
6710         end else begin
6711           // search in path
6712           IncludePath:='';
6713           PathDivider:=':;';
6714           if (Scanner.Values<>nil) then begin
6715             IncludePath:=Scanner.Values.Variables[ExternalMacroStart+'INCPATH'];
6716             if Scanner.Values.IsDefined('DELPHI') then
6717               PathDivider:=':'
6718           end;
6719           CurFilename:=SearchFileInPath(Result,BaseDir,IncludePath,PathDivider, ctsfcAllCase);
6720           if CurFilename<>'' then begin
6721             // found
6722             Result:=CreateRelativePath(CurFilename,BaseDir);
6723             Add(FilenameSrcPos,CurFilename,true);
6724           end else begin
6725             // not found
6726             Add(FilenameSrcPos,Result,false);
6727           end;
6728           //DebugLn('SearchIncludeFilename search in include path="',IncludePath,'" Result="',Result,'"');
6729         end;
6730       end else begin
6731         // unit has no path -> virtual unit -> search in virtual files
6732         ACodeBuf:=TCodeBuffer(Scanner.LoadSourceCaseLoUp(Result));
6733         if ACodeBuf<>nil then begin
6734           Result:=ACodeBuf.Filename;
6735           Add(FilenameSrcPos,Result,true);
6736         end else begin
6737           Add(FilenameSrcPos,Result,false);
6738         end;
6739       end;
6740     end;
6741   end;
6742 
6743   procedure FixFilename(StartPos, EndPos: integer);
6744   var
6745     OldFilename: String;
6746     AFilename: String;
6747   begin
6748     OldFilename:=GetForcedPathDelims(copy(ASource,StartPos,EndPos-StartPos));
6749     //DebugLn('FixFilename ',dbgs(StartPos),' ',dbgs(EndPos),' ',OldFilename);
6750     AFilename:=OldFilename;
6751     if ExtractFileExt(AFilename)='' then begin
6752       // add default extension
6753       if (Scanner.CompilerMode=cmDELPHI) then
6754         AFilename:=AFilename+'.pas'
6755       else
6756         AFilename:=AFilename+'.pp';
6757     end;
6758     AFilename:=SearchIncludeFilename(StartPos,AFilename);
6759     if OldFilename<>AFilename then begin
6760       DebugLn('TStandardCodeTool.FixIncludeFilenames.FixFilename replacing in '
6761              +Code.Filename+' include directive "',OldFilename,'" with "',AFilename,'"');
6762       SourceChangeCache.ReplaceEx(gtNone,gtNone,0,0,Code,StartPos,EndPos,AFilename);
6763     end;
6764   end;
6765 
6766 var
6767   p: Integer;
6768   NestedComments: Boolean;
6769   FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos: integer;
6770 begin
6771   Result:=false;
6772   FoundIncludeFiles:=nil;
6773   if (Scanner=nil) or (Scanner.MainCode=nil) then exit;
6774   ASource:=Code.Source;
6775   Scanner.Scan(lsrInit,false);
6776   SourceChangeCache.MainScanner:=Scanner;
6777 
6778   Result:=true;
6779   NestedComments:=Scanner.NestedComments;
6780   p:=1;
6781   repeat
6782     p:=BasicCodeTools.FindNextIncludeDirective(ASource,p,NestedComments,
6783               FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos);
6784     if (p<1) or (p>length(ASource)) then break;
6785     if (CommentStartPos=0) and (CommentEndPos=0) then ;
6786     FixFilename(FilenameStartPos,FilenameEndPos);
6787     p:=FindCommentEnd(ASource,p,NestedComments);
6788     //DebugLn('TStandardCodeTool.FixIncludeFilenames ',dbgs(p));
6789   until false;
6790   //WriteMissingIncludeFilesCodeXYPos;
6791 
6792   Result:=SourceChangeCache.Apply;
6793 end;
6794 
ReadTilGuessedUnclosedBlocknull6795 function TStandardCodeTool.ReadTilGuessedUnclosedBlock(
6796   MinCleanPos: integer;  ReadOnlyOneBlock: boolean): boolean;
6797 // returns true if unclosed block found
6798 var BlockType, CurBlockWord: TBlockKeyword;
6799   BlockStart: integer;
6800 begin
6801   Result:=false;
6802   BlockType:=bkwNone;
6803   BlockStart:=-1;
6804   // read til this block is closed
6805   while (CurPos.StartPos<=SrcLen) do begin
6806     if BlockKeywordFuncList.DoItCaseInsensitive(Src,
6807       CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
6808     begin
6809       for CurBlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
6810         if UpAtomIs(BlockKeywords[CurBlockWord]) then
6811           break;
6812       if (CurBlockWord=bkwInterface) and (not LastAtomIs(0,'=')) then
6813         CurBlockWord:=bkwNone;
6814 
6815       if (CurBlockWord=bkwEnd) then begin
6816         ReadNextAtom;
6817         if AtomIsChar('.') then begin
6818           // source end found
6819           if BlockType in [bkwBegin,bkwNone] then begin
6820             MoveCursorToCleanPos(SrcLen+1);
6821             exit;
6822           end else begin
6823             MoveCursorToCleanPos(BlockStart);
6824             Result:=true;
6825             exit;
6826           end;
6827         end else
6828           UndoReadNextAtom;
6829       end;
6830 
6831       if BlockType=bkwNone then begin
6832         case CurBlockWord of
6833 
6834         bkwBegin, bkwAsm, bkwRepeat, bkwCase, bkwTry, bkwRecord:
6835           begin
6836             BlockType:=CurBlockWord;
6837             BlockStart:=CurPos.StartPos;
6838           end;
6839 
6840         bkwClass, bkwObject, bkwInterface, bkwDispInterface:
6841           begin
6842             ReadNextAtom;
6843             if AtomIsChar(';') then begin
6844               // forward class
6845             end else if ((CurBlockWord=bkwClass) and UpAtomIs('OF')) then begin
6846               // 'class of'
6847             end else if ((CurBlockWord=bkwClass)
6848             and (UpAtomIs('FUNCTION') or UpAtomIs('PROCEDURE'))) then begin
6849               // 'class procedure'
6850             end else if ((CurBlockWord=bkwObject) and LastUpAtomIs(0,'OF')) then
6851             begin
6852               // or 'of object'
6853             end else begin
6854               BlockType:=CurBlockWord;
6855               BlockStart:=LastAtoms.GetPriorAtom.StartPos;
6856               // read ancestor list  class(...)
6857               if CurPos.Flag=cafRoundBracketOpen then begin
6858                 repeat
6859                   ReadNextAtom;
6860                   if AtomIsIdentifier then begin
6861                     ReadNextAtom;
6862                     if CurPos.Flag=cafPoint then begin
6863                       ReadNextAtom;
6864                       AtomIsIdentifierE;
6865                     end;
6866                   end;
6867                   if CurPos.Flag=cafRoundBracketClose then break;
6868                   if CurPos.Flag<>cafComma then begin
6869                     exit(false);
6870                   end;
6871                 until false;
6872                 ReadNextAtom;
6873               end;
6874               // a semicolon directly behind the ancestor list ends the class
6875               if (CurPos.Flag in [cafEnd,cafSemicolon]) then begin
6876                 // class ends
6877                 BlockType:=bkwNone;
6878               end else begin
6879                 // class continues
6880                 UndoReadNextAtom;
6881               end;
6882             end;
6883           end;
6884 
6885         bkwEnd, bkwUntil:
6886           begin
6887             // close block keywords found, but no block was opened
6888             //  -> unclosed block found
6889             Result:=true;
6890             exit;
6891           end;
6892 
6893         end;
6894       end
6895       else
6896       if ((BlockType in [bkwBegin, bkwAsm, bkwCase, bkwRecord, bkwClass,
6897         bkwObject, bkwFinally, bkwExcept, bkwInterface, bkwDispInterface])
6898         and (CurBlockWord=bkwEnd))
6899       or ((BlockType=bkwRepeat) and (CurBlockWord=bkwUntil)) then begin
6900         // block end found
6901         if (MinCleanPos<=CurPos.StartPos)
6902         and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
6903         then begin
6904           // different indent -> unclosed block found
6905           if Beautifier.GetLineIndent(Src,BlockStart)>Beautifier.GetLineIndent(Src,CurPos.StartPos)
6906           then begin
6907             // the current block is more indented than the next block
6908             // -> probably the current block misses a block end
6909             MoveCursorToCleanPos(BlockStart);
6910           end;
6911           Result:=true;
6912           exit;
6913         end;
6914         // end block
6915         if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
6916           // the 'end' keyword is the end for the case block and the record block
6917           UndoReadNextAtom;
6918         end;
6919         BlockType:=bkwNone;
6920         if ReadOnlyOneBlock then break;
6921       end
6922       else
6923       if (BlockType=bkwTry) and (CurBlockWord in [bkwFinally,bkwExcept]) then
6924       begin
6925         // try..finally, try..except found
6926         if (MinCleanPos<=CurPos.StartPos)
6927         and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
6928         then begin
6929           // different indent -> unclosed block found
6930           //   probably a block start is missing, so the error position is
6931           //   here at block end
6932           Result:=true;
6933           exit;
6934         end;
6935         // change blocktype
6936         BlockType:=CurBlockWord;
6937         BlockStart:=CurPos.StartPos;
6938       end
6939       else
6940       if ((BlockType in [bkwBegin,bkwRepeat,bkwTry,bkwFinally,bkwExcept,
6941           bkwCase])
6942         and (CurBlockWord in [bkwBegin,bkwRepeat,bkwTry,bkwCase,bkwAsm]))
6943       or ((BlockType in [bkwClass,bkwInterface,bkwDispInterface,bkwObject,
6944           bkwRecord])
6945         and (CurBlockWord in [bkwRecord])) then
6946       begin
6947         // sub blockstart found -> read recursively
6948         Result:=ReadTilGuessedUnclosedBlock(MinCleanPos,true);
6949         if Result then exit;
6950       end
6951       else
6952       if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
6953         // variant record
6954       end
6955       else
6956       if (BlockType=bkwClass) and (CurBlockWord=bkwClass) then begin
6957         // class method
6958       end
6959       else
6960       begin
6961         // unexpected keyword found
6962         if Beautifier.GetLineIndent(Src,BlockStart)>=Beautifier.GetLineIndent(Src,CurPos.StartPos)
6963         then begin
6964           // the current block is more or equal indented than the next block
6965           // -> probably the current block misses a block end
6966           MoveCursorToCleanPos(BlockStart);
6967         end;
6968         Result:=true;
6969         exit;
6970       end;
6971     end;
6972     ReadNextAtom;
6973   end;
6974 end;
6975 
RemoveApplicationScaledStatementnull6976 function TStandardCodeTool.RemoveApplicationScaledStatement(
6977   SourceChangeCache: TSourceChangeCache): boolean;
6978 begin
6979   Result := RemoveApplicationStatement('SCALED', SourceChangeCache);
6980 end;
6981 
TStandardCodeTool.RemoveApplicationStatementnull6982 function TStandardCodeTool.RemoveApplicationStatement(const APropertyUpCase: string;
6983   SourceChangeCache: TSourceChangeCache): boolean;
6984 var
6985   StartPos, ConstStartPos, EndPos: integer;
6986   OldExists: Boolean;
6987   FromPos: Integer;
6988   ToPos: Integer;
6989 begin
6990   Result:=false;
6991   // search old Application.XYZ:= statement
6992   OldExists:=FindApplicationStatement(APropertyUpCase,StartPos,ConstStartPos,EndPos);
6993   if not OldExists then
6994     exit(true);
6995   // -> delete whole line
6996   FromPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);
6997   ToPos:=FindLineEndOrCodeAfterPosition(EndPos);
6998   SourceChangeCache.MainScanner:=Scanner;
6999   if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
7000   if not SourceChangeCache.Apply then exit;
7001   Result:=true;
7002 end;
7003 
TStandardCodeTool.ReadForwardTilAnyBracketClosenull7004 function TStandardCodeTool.ReadForwardTilAnyBracketClose: boolean;
readsnull7005 // this function reads any bracket
7006 // (the ReadTilBracketClose function reads only brackets in code, not comments)
7007 var OpenBracket: char;
7008 begin
7009   Result:=false;
7010   OpenBracket:=Src[CurPos.StartPos];
7011   if OpenBracket='{' then begin
7012     // read til end of comment
7013     MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
7014     Result:=CurPos.StartPos<=SrcLen;
7015   end else if OpenBracket='(' then begin
7016     if (CurPos.StartPos<SrcLen) and (Src[CurPos.StartPos+1]='*') then begin
7017       // read til end of comment
7018       MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
7019       Result:=CurPos.StartPos<=SrcLen;
7020     end else begin
7021       // round bracket operator
7022       Result:=ReadTilBracketClose(false);
7023     end;
7024   end else if OpenBracket='[' then begin
7025     Result:=ReadTilBracketClose(false);
7026   end;
7027 end;
7028 
TStandardCodeTool.ReadBackwardTilAnyBracketClosenull7029 function TStandardCodeTool.ReadBackwardTilAnyBracketClose: boolean;
readsnull7030 // this function reads any bracket
7031 // (the ReadBackTilBracketClose function reads only brackets in code,
7032 //  not comments)
7033 var OpenBracket: char;
7034   CommentLvl: integer;
7035 begin
7036   Result:=false;
7037   OpenBracket:=Src[CurPos.StartPos];
7038   if OpenBracket='}' then begin
7039     // read backwards til end of comment
7040     dec(CurPos.StartPos);
7041     if (CurPos.StartPos>0) and (Src[CurPos.StartPos]=#3) then begin
7042       // codetools skip comment
7043       dec(CurPos.StartPos);
7044       while (CurPos.StartPos>=1) do begin
7045         if (Src[CurPos.StartPos]=#3) and (CurPos.StartPos>1)
7046         and (Src[CurPos.StartPos-1]='}') then begin
7047           dec(CurPos.StartPos,2);
7048           break;
7049         end;
7050         dec(CurPos.StartPos);
7051       end;
7052     end else begin
7053       // pascal comment
7054       CommentLvl:=1;
7055       while (CurPos.StartPos>=1) and (CommentLvl>0) do begin
7056         case Src[CurPos.StartPos] of
7057         '}': if Scanner.NestedComments then inc(CommentLvl);
7058         '{':
7059           if CommentLvl=1 then begin
7060             Result:=true;
7061             break;
7062           end else
7063             dec(CommentLvl);
7064         end;
7065         dec(CurPos.StartPos);
7066       end;
7067     end;
7068   end else if OpenBracket=')' then begin
7069     if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='*') then begin
7070       // read til end of comment
7071       dec(CurPos.StartPos,3);
7072       while true do begin
7073         if (CurPos.StartPos>=1)
7074         and ((Src[CurPos.StartPos+1]='*') and (Src[CurPos.StartPos]='(')) then
7075         begin
7076           Result:=true;
7077           exit;
7078         end;
7079         dec(CurPos.StartPos);
7080       end;
7081     end else begin
7082       Result:=ReadBackTilBracketOpen(false);
7083     end;
7084   end else if OpenBracket=']' then begin
7085     Result:=ReadBackTilBracketOpen(false);
7086   end;
7087 end;
7088 
Explorenull7089 function TStandardCodeTool.Explore(WithStatements: boolean;
7090   Range: TLinkScannerRange): boolean;
7091 var
7092   Node: TCodeTreeNode;
7093 begin
7094   Result:=true;
7095   BuildTree(Range);
7096   Node:=Tree.Root;
7097   while Node<>nil do begin
7098     case Node.Desc of
7099     ctnProcedure,ctnProcedureHead:
7100       BuildSubTreeForProcHead(Node);
7101     ctnBeginBlock:
7102       if WithStatements then
7103         BuildSubTreeForBeginBlock(Node);
7104     ctnImplementation:
7105       if ord(Range)<ord(lsrImplementationStart) then exit;
7106     end;
7107     Node:=Node.Next;
7108   end;
7109 end;
7110 
Explorenull7111 function TStandardCodeTool.Explore(WithStatements: boolean;
7112   OnlyInterface: boolean): boolean;
7113 begin
7114   if OnlyInterface then
7115     Result:=Explore(WithStatements,lsrImplementationStart)
7116   else
7117     Result:=Explore(WithStatements,lsrEnd);
7118 end;
7119 
7120 finalization
7121   FreeAndNil(BlockKeywordFuncList);
7122 
7123 end.
7124 
7125 
7126