{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    TStandardCodeTool enhances TIdentCompletionTool with many standard code
    editing functions for the following categories:
      - source name
      - uses sections
      - lazarus resources
      - Application.CreateForm statements
      - published variables
      - resource strings
      - compiler and IDE directives
      - code exploring
      - code blocks
}
unit StdCodeTools;

{$ifdef FPC}{$mode objfpc}{$endif}{$H+}

interface

{$I codetools.inc}

{ $DEFINE VerboseGetStringConstBounds}
{ $DEFINE VerboseCompleteBlock}
{ $DEFINE VerboseCheckLFM}
{ $DEFINE VerboseFindUnusedUnits}

uses
  {$IFDEF MEM_CHECK}
  MemCheck,
  {$ENDIF}
  Classes, SysUtils, TypInfo, Laz_AVL_Tree,
  // Codetools
  CodeToolsStrConsts, FileProcs, CodeTree, CodeAtom,
  FindDeclarationTool, IdentCompletionTool, PascalReaderTool, PascalParserTool,
  ExprEval, KeywordFuncLists, BasicCodeTools, LinkScanner,
  CodeCache, LFMTrees, SourceChanger, CustomCodeTool, CodeToolsStructs,
  // LazUtils
  LazFileUtils, LazFileCache, LazUTF8, AvgLvlTree;

type
  TStandardCodeTool = class;

  TInsertStatementPosDescription = class
  public
    InsertPos: integer;
    Indent: integer;
    CodeXYPos: TCodeXYPosition;
    FrontGap, AfterGap: TGapTyp;
    Description: string;
  end;

  TUsesSection = (usMain, usImplementation);
  TAddUsesFlag = (
    aufLast,
    aufNotCheckSpecialUnit
    );
  TAddUsesFlags = set of TAddUsesFlag;

  TOnFindDefinePropertyForContext = procedure(Sender: TObject;
    const ClassContext, AncestorClassContext: TFindContext;
    LFMNode: TLFMTreeNode;
    const IdentName: string; var IsDefined: boolean) of object;
  TOnIDEDirectiveFilter = function(Tool: TStandardCodeTool;
    StartPos, EndPos: integer): boolean of object; // true = use

  { TStandardCodeTool }

  TStandardCodeTool = class(TIdentCompletionTool)
  private
    function ReadTilGuessedUnclosedBlock(MinCleanPos: integer;
      ReadOnlyOneBlock: boolean): boolean;
    function ReadForwardTilAnyBracketClose: boolean;
    function ReadBackwardTilAnyBracketClose: boolean;
    function FindApplicationStatement(const APropertyUpCase: string;
          out StartPos, ConstStartPos, EndPos: integer): boolean;
    function SetApplicationStatement(const APropertyName, NewCode: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveApplicationStatement(const APropertyUpCase: string;
          SourceChangeCache: TSourceChangeCache): boolean;
  public
    // explore the code
    function Explore(WithStatements: boolean; Range: TLinkScannerRange): boolean;
    function Explore(WithStatements: boolean;
          OnlyInterface: boolean = false): boolean;
  
    // source name  e.g. 'unit UnitName;'
    function GetCachedSourceName: string;
    function RenameSource(const NewName: string;
          SourceChangeCache: TSourceChangeCache): boolean;
        
    // uses sections
    function RenameUsedUnit(const OldUnitName, NewUnitName, NewUnitInFile: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function ReplaceUsedUnits(UnitNamePairs: TStringToStringTree; // ToDo: dotted
          SourceChangeCache: TSourceChangeCache): boolean;
    function AddUnitToUsesSection(UsesNode: TCodeTreeNode;
          const NewUnitName, NewUnitInFile: string;
          SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
    function AddUnitToSpecificUsesSection(UsesSection: TUsesSection;
          const NewUnitName, NewUnitInFile: string;
          SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
    function AddUnitToMainUsesSection(const NewUnitName, NewUnitInFile: string;
          SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
    function AddUnitToImplementationUsesSection(const NewUnitName, NewUnitInFile: string;
          SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags = []): boolean;
    function UnitExistsInUsesSection(UsesSection: TUsesSection;
          const AnUnitName: string): boolean;
    function UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
                                const AnUnitName: string): boolean;
    function RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
                                const AnUnitName: string;
                                SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveUnitFromAllUsesSections(const AnUnitName: string;
                                SourceChangeCache: TSourceChangeCache): boolean;
    function FixUsedUnitCase(SourceChangeCache: TSourceChangeCache): boolean;
    function FixUsedUnitCaseInUsesSection(UsesNode: TCodeTreeNode;
                                SourceChangeCache: TSourceChangeCache): boolean;
    function FindUsedUnitNames(var MainUsesSection,
                               ImplementationUsesSection: TStrings): boolean;
    function FindUsedUnitNames(var List: TStringToStringTree): boolean;
    function FindUsedUnitFiles(var MainUsesSection: TStrings): boolean;
    function FindUsedUnitFiles(var MainUsesSection,
                               ImplementationUsesSection: TStrings): boolean;
    function FindDelphiProjectUnits(out FoundInUnits, MissingInUnits,
                                    NormalUnits: TStrings;
                                    UseContainsSection: boolean = false;
                                    IgnoreNormalUnits: boolean = false): boolean;
    function UsesSectionToFilenames(UsesNode: TCodeTreeNode): TStrings;
    function UsesSectionToUnitnames(UsesNode: TCodeTreeNode): TStrings;
    function FindMissingUnits(var MissingUnits: TStrings; FixCase: boolean;
                              SearchImplementation: boolean;
                              SourceChangeCache: TSourceChangeCache): boolean;
    function CommentUnitsInUsesSection(MissingUnits: TStrings;
      SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
    function CommentUnitsInUsesSections(MissingUnits: TStrings;
                                SourceChangeCache: TSourceChangeCache): boolean;
    function FindUnusedUnits(Units: TStrings): boolean;

    // lazarus resources
    function FindNextIncludeInInitialization(
          var LinkIndex: integer): TCodeBuffer;
    function FindLazarusResourceInBuffer(ResourceCode: TCodeBuffer;
          const ResourceName: string): TAtomPosition;
    function FindLazarusResource(const ResourceName: string): TAtomPosition;
    function AddLazarusResource(ResourceCode: TCodeBuffer;
          const ResourceName, ResourceData: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveLazarusResource(ResourceCode: TCodeBuffer;
          const ResourceName: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RenameInclude(LinkIndex: integer; const NewFilename: string;
          KeepPath: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
          const OnFindDefineProperty: TOnFindDefinePropertyForContext;
          RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
          ObjectsMustExist: boolean): boolean;

    // Application.Createform statements
    function FindCreateFormStatement(StartPos: integer;
          const AClassName, AVarName: string;
          out Position: TAtomPosition): integer; // 0=found, -1=not found, 1=found, but wrong classname
    function AddCreateFormStatement(const AClassName, AVarName: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveCreateFormStatement(const AVarName: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function ChangeCreateFormStatement(StartPos: integer;
          const OldClassName, OldVarName: string;
          const NewClassName, NewVarName: string;
          OnlyIfExists: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function ListAllCreateFormStatements: TStrings;
    function SetAllCreateFromStatements(List: TStrings;
          SourceChangeCache: TSourceChangeCache): boolean;    
          
    // Application.Title:=<string const> statements
    function FindApplicationTitleStatement(out StartPos, StringConstStartPos,
          EndPos: integer): boolean;
    function GetApplicationTitleStatement(StringConstStartPos, EndPos: integer;
          var Title: string): boolean;
    function SetApplicationTitleStatement(const NewTitle: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveApplicationTitleStatement(
          SourceChangeCache: TSourceChangeCache): boolean;

    // Application.Scaled:=<boolean const> statements
    function FindApplicationScaledStatement(out StartPos, BooleanConstStartPos,
          EndPos: integer): boolean;
    function GetApplicationScaledStatement(BooleanConstStartPos, EndPos: integer;
          var AScaled: boolean): boolean;
    function SetApplicationScaledStatement(const NewScaled: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RemoveApplicationScaledStatement(
          SourceChangeCache: TSourceChangeCache): boolean;

    // forms
    function RenameForm(const OldFormName, OldFormClassName: string;
          const NewFormName, NewFormClassName: string;
          SourceChangeCache: TSourceChangeCache): boolean;
    function FindFormAncestor(const AClassName: string;
          out AncestorClassName: string): boolean;

    // published variables
    function FindPublishedVariable(const AClassName, AVarName: string;
          ExceptionOnClassNotFound: boolean): TCodeTreeNode;
    function GatherPublishedVarTypes(const AClassName: string;
          out VarNameToType: TStringToStringTree): boolean;
    function AddPublishedVariable(const AClassName,VarName, VarType: string;
          SourceChangeCache: TSourceChangeCache): boolean; virtual;
    function RemovePublishedVariable(const AClassName, AVarName: string;
          ExceptionOnClassNotFound: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function RenamePublishedVariable(const AClassName,
          AOldVarName: string; const NewVarName, VarType: shortstring;
          ExceptionOnClassNotFound: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function GatherPublishedClassElements(const TheClassName: string;
          ExceptionOnClassNotFound, WithVariables, WithMethods,
          WithProperties, WithAncestors: boolean;
          out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
    function RetypeClassVariables(const AClassName: string;
          ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
          SourceChangeCache: TSourceChangeCache;
          SearchImplementationToo: boolean = false): boolean;
    function FindDanglingComponentEvents(const TheClassName: string;
          RootComponent: TComponent; ExceptionOnClassNotFound,
          SearchInAncestors: boolean;
          out ListOfPInstancePropInfo: TFPList;
          const OverrideGetMethodName: TOnGetMethodname = nil): boolean;

    // variables, constants, types
    function RemoveIdentifierDefinition(const CursorPos: TCodeXYPosition;
          SourceChangeCache: TSourceChangeCache): boolean;

    function InsertStatements(InsertPos: TInsertStatementPosDescription;
          Statements: string; SourceChangeCache: TSourceChangeCache): boolean;
    function InsertStatements(CleanPos: integer;
          Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
          SourceChangeCache: TSourceChangeCache): boolean;

    // blocks (e.g. begin..end)
    function FindBlockCounterPart(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer;
          IncludeCloseToken: Boolean = false): boolean;
    function FindBlockStart(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer;
          SkipStart: boolean = true): boolean;
    function GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
    function FindBlockCleanBounds(const CursorPos: TCodeXYPosition;
          out BlockCleanStart, BlockCleanEnd: integer): boolean;
    function CompleteBlock(const CursorPos: TCodeXYPosition;
          SourceChangeCache: TSourceChangeCache;
          OnlyIfCursorBlockIndented: boolean;
          out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;

    // compiler directives
    {$IFDEF GuessMisplacedIfdef}
    function GuessMisplacedIfdefEndif(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
    {$ENDIF}
    function FindEnclosingIncludeDirective(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
    function FindModeDirective(DoBuildTree: boolean;
          out ACleanPos: integer): boolean;
    function FindResourceDirective(DoBuildTree: boolean;
          var ACleanPos: integer; const Filename: string = ''): boolean;
    function FindResourceDirective(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer;
          const Filename: string = ''): boolean;
    function AddResourceDirective(const Filename: string;
          SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
          ): boolean;
    function FindIncludeDirective(DoBuildTree: boolean;
          var ACleanPos: integer; const Filename: string = ''): boolean;
    function FindIncludeDirective(const CursorPos: TCodeXYPosition;
          out NewPos: TCodeXYPosition; out NewTopLine: integer;
          const Filename: string = ''): boolean;
    function AddIncludeDirectiveForInit(const Filename: string;
          SourceChangeCache: TSourceChangeCache; const NewSrc: string = ''
          ): boolean;
    function AddUnitWarnDirective(WarnID, Comment: string; TurnOn: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    function FixIncludeFilenames(Code: TCodeBuffer;
          SourceChangeCache: TSourceChangeCache;
          out FoundIncludeFiles: TStrings;
          var MissingIncludeFilesCodeXYPos: TFPList): boolean;

    // search & replace
    function ReplaceWords(IdentList: TStrings; ChangeStrings: boolean;
          SourceChangeCache: TSourceChangeCache;
          SkipPointWords: boolean = false): boolean;
    function FindNearestIdentifierNode(const CursorPos: TCodeXYPosition;
          IdentTree: TAVLTree): TAVLTreeNode;
    function ReplaceWord(const OldWord, NewWord: string; ChangeStrings: boolean;
          SourceChangeCache: TSourceChangeCache;
          SkipPointWords: boolean = false): boolean;

    // comments
    function CommentCode(const StartPos, EndPos: integer;
          SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;

    // expressions
    function GetStringConstBounds(const CursorPos: TCodeXYPosition;
          out StartPos, EndPos: TCodeXYPosition;
          ResolveComments: boolean): boolean;
    function GetStringConstAsFormatString(StartPos, EndPos: integer;
          out FormatStringConstant, FormatParameters: string;
          out StartInStringConst, EndInStringConst: boolean): boolean;
    function GetStringConstAsFormatString(StartPos, EndPos: integer;
          out FormatStringConstant, FormatParameters: string): boolean;
    function ExtractOperand(const CursorPos: TCodeXYPosition;
          out Operand: string; WithPostTokens, WithAsOperator,
          WithoutTrailingPoints: boolean): boolean;

    // resource strings
    function GatherResourceStringSections(const CursorPos: TCodeXYPosition;
          PositionList: TCodeXYPositions): boolean;
    function IdentifierExistsInResourceStringSection(
          const CursorPos: TCodeXYPosition;
          const ResStrIdentifier: string): boolean;
    function GatherResourceStringsWithValue(const CursorPos: TCodeXYPosition;
          const StringValue: string;
          PositionList: TCodeXYPositions): boolean;
    function GatherResourceStringIdents(const SectionPos: TCodeXYPosition;
          var IdentTree: TAVLTree): boolean;
    function FindNearestResourceString(const CursorPos,
          SectionPos: TCodeXYPosition;
          var NearestPos: TCodeXYPosition): boolean;
    function AddResourceString(const SectionPos: TCodeXYPosition;
          const NewIdentifier, NewValue: string;
          InsertPolicy: TResourcestringInsertPolicy;
          const NearestPos: TCodeXYPosition;
          SourceChangeCache: TSourceChangeCache): boolean;
    function CreateIdentifierFromStringConst(
          const StartCursorPos, EndCursorPos: TCodeXYPosition;
          out Identifier: string; MaxLen: integer): boolean;
    function StringConstToFormatString(
          const StartCursorPos, EndCursorPos: TCodeXYPosition;
          out FormatStringConstant,FormatParameters: string;
          out StartInStringConst, EndInStringConst: boolean): boolean;
          
    // register procedure
    function HasInterfaceRegisterProc: boolean;
    
    // Delphi to Lazarus conversion
    function ConvertDelphiToLazarusSource(AddLRSCode: boolean;
          SourceChangeCache: TSourceChangeCache): boolean;
    
    // IDE % directives
    function GetIDEDirectives(DirectiveList: TStrings;
          const Filter: TOnIDEDirectiveFilter = nil): boolean;
    function SetIDEDirectives(DirectiveList: TStrings;
          SourceChangeCache: TSourceChangeCache;
          const Filter: TOnIDEDirectiveFilter = nil): boolean;

    // debugging
    procedure CalcMemSize(Stats: TCTMemStats); override;
  end;


implementation


type
  TBlockKeyword = (bkwNone, bkwBegin, bkwAsm, bkwTry, bkwCase, bkwRepeat,
                   bkwRecord, bkwClass, bkwObject, bkwInterface,
                   bkwDispInterface, bkwEnd, bkwUntil, bkwFinally,
                   bkwExcept);

const
  BlockKeywords: array[TBlockKeyword] of string = (
      '(unknown)', 'BEGIN', 'ASM', 'TRY', 'CASE', 'REPEAT', 'RECORD', 'CLASS',
      'OBJECT', 'INTERFACE', 'DISPINTERFACE', 'END', 'UNTIL', 'FINALLY',
      'EXCEPT'
    );

var
  BlockKeywordFuncList: TKeyWordFunctionList;
  
procedure BuildBlockKeyWordFuncList;
var BlockWord: TBlockKeyword;
begin
  if BlockKeywordFuncList=nil then begin
    BlockKeywordFuncList:=TKeyWordFunctionList.Create('StdCodeTools.BlockKeywordFuncList');
    for BlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
      with BlockKeywordFuncList do
        Add(BlockKeywords[BlockWord],{$ifdef FPC}@{$endif}AllwaysTrue);
  end;
end;


{ TStandardCodeTool }

{-------------------------------------------------------------------------------
  function TStandardCodeTool.GetCachedSourceName: string;
  Params: none
  Result: the source name (= e.g. the identifier behind 'program'/'unit' keyword)

  This function does neither check if source needs reparsing, nor does it check
  for errors in code. It simple checks if there is a first node, which is
  typically the source type and name.
  This function can therefore be used as a fast GetSourceName function.
-------------------------------------------------------------------------------}
function TStandardCodeTool.GetCachedSourceName: string;
begin
  Result:=GetSourceName(false);
end;

function TStandardCodeTool.RenameSource(const NewName: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  NamePos: TAtomPosition;
begin
  Result:=false;
  BuildTree(lsrSourceName);
  //debugln(['TStandardCodeTool.RenameSource NewName=',NewName]);
  if (not GetSourceNamePos(NamePos)) or (NamePos.StartPos<1) or (NewName='')
  or (Length(NewName)>255) then exit;
  //debugln(['TStandardCodeTool.RenameSource OldName="',dbgstr(copy(Src,NamePos.StartPos,NamePos.EndPos-NamePos.StartPos)),'"']);
  SourceChangeCache.MainScanner:=Scanner;
  SourceChangeCache.Replace(gtNone,gtNone,NamePos.StartPos,NamePos.EndPos,NewName);
  if not SourceChangeCache.Apply then exit;
  CachedSourceName:=NewName;
  Result:=true;
end;

function TStandardCodeTool.RenameUsedUnit(const OldUnitName, NewUnitName,
  NewUnitInFile: string; SourceChangeCache: TSourceChangeCache): boolean;
var
  UnitPos, InPos: TAtomPosition;
  NewUsesTerm: string;
begin
  Result:=false;
  if (not IsDottedIdentifier(OldUnitName))
  or (not IsDottedIdentifier(NewUnitName)) then
    exit;
  if not FindUnitInAllUsesSections(OldUnitName,UnitPos,InPos) then begin
    //debugln('TStandardCodeTool.RenameUsedUnit not found: ',OldUnitName,' ');
    exit;
  end;
  SourceChangeCache.MainScanner:=Scanner;
  if InPos.StartPos>0 then
    UnitPos.EndPos:=InPos.EndPos;
  // build use unit term
  NewUsesTerm:=NewUnitName;
  if NewUnitInFile<>'' then
    NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+'''';
  // Note: do not use beautifier, unit names are case sensitive
  if ReplacementNeedsLineEnd(Src,UnitPos.StartPos,UnitPos.EndPos,
    length(NewUsesTerm),SourceChangeCache.BeautifyCodeOptions.LineLength) then
  begin
    if not SourceChangeCache.Replace(gtNewLine,gtNone,
      UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
  end else begin
    if not SourceChangeCache.Replace(gtSpace,gtNone,
      UnitPos.StartPos,UnitPos.EndPos,NewUsesTerm) then exit;
  end;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.ReplaceUsedUnits(UnitNamePairs: TStringToStringTree;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  ExistingUnits: TStringToStringTree;
  
  procedure CleanNewUnits(const AnUnitName: string; var NewText: string);
  var
    StartPos: Integer;
    EndPos: LongInt;
    CommaBehind: LongInt;
    CommaInFront: Integer;
    NewUnitName: String;
  begin
    // remove all units, that already exists
    StartPos:=1;
    CommaInFront:=-1;
    while StartPos<=length(NewText) do begin
      EndPos:=StartPos;
      while (EndPos<=length(NewText)) and (IsIdentChar[NewText[EndPos]]) do
        inc(EndPos);
      if EndPos<=StartPos then break;
      NewUnitName:=copy(NewText,StartPos,EndPos-StartPos);
      // set EndPos to start of next unit
      CommaBehind:=-1;
      while (EndPos<=length(NewText)) do begin
        if NewText[EndPos]='''' then begin
          inc(EndPos);
          while (EndPos<=length(NewText)) and (NewText[EndPos]<>'''') do
            inc(EndPos);
        end else if NewText[EndPos]=',' then begin
          CommaBehind:=EndPos;
          while (EndPos<=length(NewText))
          and (not IsIdentStartChar[NewText[EndPos]]) do
            inc(EndPos);
          break;
        end;
        inc(EndPos);
      end;
      if (SysUtils.CompareText(AnUnitName,NewUnitName)=0) then begin
        // this is the old unit or
        //DebugLn('Replace: keep old unit "',NewUnitName,'"');
      end else if ExistingUnits.Contains(NewUnitName) then begin
        // this unit already exists and should not be added
        //DebugLn('Replace: already exists: "',NewUnitName,'"="',ExistingUnits[NewUnitName],'" CommaInFront=',dbgs(CommaInFront),' CommaBehind=',dbgs(CommaBehind));
        if CommaBehind>0 then
          System.Delete(NewText,StartPos,EndPos-StartPos)
        else if CommaInFront>0 then
          System.Delete(NewText,CommaInFront,EndPos-CommaInFront)
        else
          System.Delete(NewText,StartPos,EndPos-StartPos);
        EndPos:=StartPos;
        CommaBehind:=-1;
      end else begin
        // this unit does not exist yet
        //DebugLn('Replace new unit with "',NewUnitName,'"');
      end;
      if CommaBehind>0 then
        CommaInFront:=CommaBehind;
      StartPos:=EndPos;
    end;
  end;

  function Replace(UsesNode: TCodeTreeNode): boolean;
  var
    UnitNameAtom: TAtomPosition;
    InAtom: TAtomPosition;
    NewText: string;
    CommaInFront: LongInt;
    FromPos: LongInt;
    ToPos: LongInt;
    CommaBehind: Integer;
    AnUnitName: String;
  begin
    if UsesNode=nil then exit(true);
    MoveCursorToUsesStart(UsesNode);
    CommaInFront:=-1;
    repeat
      // read next unit name
      ReadNextUsedUnit(UnitNameAtom, InAtom);
      if CurPos.Flag=cafComma then
        CommaBehind:=CurPos.StartPos
      else
        CommaBehind:=-1;
      AnUnitName:=GetAtom(UnitNameAtom);
      if UnitNamePairs.Contains(AnUnitName) then begin
        // replace
        NewText:=UnitNamePairs[AnUnitName];
        //DebugLn('Replace Unit="',AnUnitName,'" NewText="',NewText,'"');
        
        CleanNewUnits(AnUnitName,NewText);
        
        if NewText='' then begin
          // comment unit
          if CommaInFront>0 then begin
            // example:  uses a{, b};
            FromPos:=CommaInFront;
            ToPos:=UnitNameAtom.EndPos;
            if InAtom.StartPos>0 then
              ToPos:=InAtom.EndPos;
          end else if CommaBehind>0 then begin
            // example:  uses {a,} b;
            //           uses {a,} {b};
            FromPos:=UnitNameAtom.StartPos;
            ToPos:=CommaBehind+1;
          end else begin
            // examples:  uses {b};
            FromPos:=UnitNameAtom.StartPos;
            ToPos:=UnitNameAtom.EndPos;
            if InAtom.StartPos>0 then
              ToPos:=InAtom.EndPos;
          end;
          if not CommentCode(FromPos,ToPos,SourceChangeCache,false) then
            exit(false);
        end else begin
          // replace
          FromPos:=UnitNameAtom.StartPos;
          ToPos:=UnitNameAtom.EndPos;
          if InAtom.StartPos>0 then
            ToPos:=InAtom.EndPos;
          if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewText)
          then exit(false);
        end;
      end;

      if CurPos.Flag=cafComma then begin
        // read next unit name
        CommaInFront:=CurPos.StartPos;
        ReadNextAtom;
      end else if CurPos.Flag=cafSemicolon then begin
        break;
      end else
        RaiseExceptionFmt(20170421201056,ctsStrExpectedButAtomFound,[';',GetAtom]);
    until false;
    Result:=true;
  end;
  
begin
  Result:=false;
  BuildTree(lsrImplementationUsesSectionEnd);
  SourceChangeCache.MainScanner:=Scanner;
  ExistingUnits:=nil;
  try
    // first collect all units
    if not FindUsedUnitNames(ExistingUnits) then exit;
    // then change uses sections
    Replace(FindMainUsesNode);
    Replace(FindImplementationUsesNode);
  finally
    ExistingUnits.Free;
  end;
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.AddUnitToUsesSection(UsesNode: TCodeTreeNode;
  const NewUnitName, NewUnitInFile: string;
  SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
const
  SpecialUnits: array[1..5] of string = (
    'cmem',
    'sharedmem',
    'lineinfo',
    'heaptrc',
    'cthreads'
    );
var
  Beauty: TBeautifyCodeOptions;

  function SpecialUnitPriority(Identifier: PChar): integer;
  begin
    Result:=Low(SpecialUnits);
    while Result<=High(SpecialUnits) do begin
      if CompareIdentifierPtrs(Pointer(Identifier),Pointer(SpecialUnits[Result]))=0 then
        exit;
      inc(Result);
    end;
  end;

  function NextUseUnitNodeInSameBlock(Node: TCodeTreeNode): boolean;
  var
    p: LongInt;
  begin
    if Node.NextBrother=nil then exit(false);
    if PositionsInSameLine(Src,Node.EndPos,Node.NextBrother.StartPos) then
    begin
      // uses on same line belongs to the same formatting block
      exit(true);
    end;
    // check that there is no comment/directive between
    p:=FindPrevNonSpace(Src,Node.NextBrother.StartPos-1);
    if Src[p]<>',' then exit(false);
    p:=FindPrevNonSpace(Src,p-1);
    if p>Node.EndPos then exit(false);
    if LineEndCount(Src,Node.EndPos,Node.NextBrother.StartPos,p)>1 then exit(false);
    Result:=true;
  end;

  procedure AddUseUnit(Lines: TStrings; FirstIndent, Indent: integer;
    const NewUses: string);
  var
    Line: string;
    l: Integer;
  begin
    if Lines.Count=0 then begin
      Lines.Add(NewUses);
      exit;
    end;
    Line:=Lines[Lines.Count-1];
    if (atIdentifier in Beauty.DoInsertSpaceAfter)
    or (atComma in Beauty.DoInsertSpaceInFront) then
      Line:=Line+' ';
    Line:=Line+',';
    l:=length(Line)+length(NewUses)+1; // +1 for the following , or ;
    if (atComma in Beauty.DoInsertSpaceAfter)
    or (atIdentifier in Beauty.DoInsertSpaceInFront) then
      inc(l);
    if Lines.Count=1 then
      inc(l,FirstIndent);
    //DebugLn(['AddUseUnit Lines.Count=',Lines.Count,' l=',l,' Line="',Line,'" NewUses=',NewUses,' FirstIndent=',FirstIndent]);
    if l<=Beauty.LineLength then begin
      // append to last line
      if (atComma in Beauty.DoInsertSpaceAfter)
      or (atIdentifier in Beauty.DoInsertSpaceInFront) then
        Line:=Line+' ';
      Line:=Line+NewUses;
      Lines[Lines.Count-1]:=Line;
    end else begin
      // add new line
      Lines[Lines.Count-1]:=Line;
      Line:=Beauty.GetIndentStr(Indent)+NewUses;
      Lines.Add(Line);
    end;
  end;

var
  LineStart, LineEnd, Indent, InsertPos, InsertToPos, InsertLen: integer;
  NewUsesTerm: string;
  InsertBehind: Boolean;
  InsertNode: TCodeTreeNode;
  Node: TCodeTreeNode;
  NewCode: TCodeBuffer;
  DiffPath: String;
  DiffCnt: Integer;
  BestDiffCnt: LongInt;
  AnUnitName: String;
  AnUnitInFilename: String;
  i: Integer;
  NewFilename: String;
  NewComma: string;
  Lines: TStringList;
  FirstIndent: Integer;
  InsertCode: String;
  UsesInsertPolicy: TUsesInsertPolicy;
  Prio: LongInt;
  FirstNormalUsesNode: TCodeTreeNode;
  InsertPosFound, CheckSpecialUnits: Boolean;
begin
  Result:=false;
  if (UsesNode=nil) or (UsesNode.Desc<>ctnUsesSection)
  or (UsesNode.StartPos<1) or (UsesNode.EndPos<1)
  or (not IsDottedIdentifier(NewUnitName))
  then exit;
  SourceChangeCache.MainScanner:=Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;

  // find nice insert position

  Prio:=SpecialUnitPriority(PChar(NewUnitName));
  UsesInsertPolicy:=Beauty.UsesInsertPolicy;
  if aufLast in Flags then
    UsesInsertPolicy:=uipLast;
  InsertPosFound:=false;
  CheckSpecialUnits:=not (aufNotCheckSpecialUnit in Flags);
  if CheckSpecialUnits and (Prio<=High(SpecialUnits)) then begin
    // this is a special unit, insert at the beginning
    InsertBehind:=false;
    InsertNode:=UsesNode.FirstChild;
    while (InsertNode<>nil)
    and (Prio>SpecialUnitPriority(@Src[InsertNode.StartPos])) do
      InsertNode:=InsertNode.NextBrother;
    InsertPosFound:=true;
    if InsertNode=nil then begin
      InsertBehind:=true;
      InsertNode:=UsesNode.LastChild;
    end;
  end;
  if not InsertPosFound then begin
    FirstNormalUsesNode:=UsesNode.FirstChild;
    if CheckSpecialUnits and (UsesInsertPolicy<>uipLast) then begin
      while (FirstNormalUsesNode<>nil)
      and (SpecialUnitPriority(@Src[FirstNormalUsesNode.StartPos])<Prio) do
        FirstNormalUsesNode:=FirstNormalUsesNode.NextBrother;
      if FirstNormalUsesNode=nil then
        UsesInsertPolicy:=uipLast;
    end;

    case UsesInsertPolicy of

    uipFirst:
      begin
        InsertBehind:=false;
        InsertNode:=FirstNormalUsesNode;
      end;

    uipInFrontOfRelated,uipBehindRelated:
      begin
        if UsesInsertPolicy=uipBehindRelated then begin
          InsertNode:=UsesNode.LastChild;
          InsertBehind:=true;
        end else begin
          InsertBehind:=false;
          InsertNode:=FirstNormalUsesNode;
        end;
        NewCode:=FindUnitSource(NewUnitName,'',false);
        if NewCode<>nil then begin
          NewFilename:=NewCode.Filename;
          BestDiffCnt:=High(integer);
          Node:=FirstNormalUsesNode;
          while Node<>nil do begin
            AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
            // search unit
            //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Unit=',AnUnitName,' in "',AnUnitInFilename,'"']);
            NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false);
            if NewCode<>nil then begin
              // used unit found -> compute distance
              DiffPath:=CreateRelativePath(NewCode.Filename,ExtractFilePath(NewFilename));
              DiffCnt:=0;
              for i:=1 to length(DiffPath) do
                if DiffPath[i]=PathDelim then
                  inc(DiffCnt);
              //DebugLn(['TStandardCodeTool.AddUnitToUsesSection DiffCnt=',DiffCnt,' "',NewCode.Filename,'" "',NewFilename,'"']);
              if UsesInsertPolicy=uipInFrontOfRelated then begin
                // insert in front of the first node with the lowest DiffCnt
                if BestDiffCnt>DiffCnt then begin
                  BestDiffCnt:=DiffCnt;
                  InsertNode:=Node;
                  InsertBehind:=false;
                end;
              end else begin
                // insert behind the last node with the lowest DiffCnt
                if BestDiffCnt>=DiffCnt then begin
                  BestDiffCnt:=DiffCnt;
                  InsertNode:=Node;
                  InsertBehind:=true;
                end;
              end;
            end;
            Node:=Node.NextBrother;
          end;
        end;
      end;

    uipLast:
      begin
        InsertNode:=UsesNode.LastChild;
        InsertBehind:=true;
      end;

    uipAlphabetically:
      begin
        InsertNode:=FirstNormalUsesNode;
        InsertBehind:=false;
        while (InsertNode<>nil)
        and (CompareIdentifiers(PChar(NewUnitName),@Src[InsertNode.StartPos])<0) do
          InsertNode:=InsertNode.NextBrother;
        if InsertNode=nil then begin
          InsertNode:=UsesNode.LastChild;
          InsertBehind:=true;
        end;
      end;

    end;
  end;

  // build insert text  "newunitname in 'file'"
  NewUsesTerm:=NewUnitName;
  if NewUnitInFile<>'' then
    NewUsesTerm:=NewUsesTerm+' '
      +Beauty.BeautifyKeyWord('in')
      +' '''+NewUnitInFile+'''';

  NewComma:=',';
  if (atComma in Beauty.DoInsertSpaceInFront)
    or (atIdentifier in Beauty.DoInsertSpaceAfter)
  then
    NewComma:=' '+NewComma;
  if (atComma in Beauty.DoInsertSpaceAfter)
    or (atIdentifier in Beauty.DoInsertSpaceInFront)
  then
    NewComma:=NewComma+' ';

  if InsertBehind then begin
    // insert behind unit name, in front of semicolon or comma
    // for example: uses unit1|, unit2 in 'unit2.pp'|;
    InsertPos:=InsertNode.EndPos;
    InsertCode:=NewComma+NewUsesTerm;
  end else begin
    // insert in front of unit name, behind 'uses' or comma
    // for example: uses |unit1, |unit2;
    InsertPos:=InsertNode.StartPos;
    InsertCode:=NewUsesTerm+NewComma;
  end;
  InsertToPos:=InsertPos;

  //DebugLn(['TStandardCodeTool.AddUnitToUsesSection InsertNode=',ExtractNode(InsertNode,[]),' InsertBehind=',InsertBehind]);

  // check if addition fits into the line
  // if not, rebuild the uses section
  GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
  InsertLen:=length(NewUsesTerm)+length(NewComma);
  //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Line=',copy(Src,LineStart,InsertPos-LineStart),'<InsertPos>',copy(Src,InsertPos,LineEnd-InsertPos),' NewLen=',LineEnd-LineStart+InsertLen,' Max=',Beauty.LineLength,' Addition=',NewUsesTerm]);
  if (LineEnd-LineStart+InsertLen > Beauty.LineLength) then begin
    // line too long => reformat block of used units
    // find start of block of used units
    Node:=InsertNode;
    while (Node.PriorBrother<>nil)
    and NextUseUnitNodeInSameBlock(Node.PriorBrother) do
      Node:=Node.PriorBrother;
    InsertPos:=Node.StartPos;
    GetLineStartEndAtPosition(Src,InsertPos,LineStart,LineEnd);
    FirstIndent:=InsertPos-LineStart;
    Indent:=Beauty.GetLineIndent(Src,InsertPos);
    if PositionsInSameLine(Src,UsesNode.StartPos,InsertPos) then begin
      // for example: uses |unit1;
      inc(Indent,Beauty.Indent);
    end;
    // create new block of used units
    Lines:=TStringList.Create;
    try
      while Node<>nil do begin
        InsertToPos:=Node.EndPos;
        if (Node=InsertNode) and (not InsertBehind) then
          AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
        InsertCode:=ExtractUsedUnitName(Node);
        if UpAtomIs('IN') then begin
          ReadNextAtom;
          InsertCode:=InsertCode+' '+Beauty.BeautifyKeyWord('in')+' '+GetAtom;
        end;
        AddUseUnit(Lines,FirstIndent,Indent,InsertCode);
        if (Node=InsertNode) and InsertBehind then
          AddUseUnit(Lines,FirstIndent,Indent,NewUsesTerm);
        if not NextUseUnitNodeInSameBlock(Node) then break;
        Node:=Node.NextBrother;
      end;
      InsertCode:='';
      for i:=0 to Lines.Count-1 do begin
        if i>0 then
          InsertCode:=InsertCode+Beauty.LineEnd;
        InsertCode:=InsertCode+Lines[i];
      end;
    finally
      Lines.Free;
    end;
  end;

  //DebugLn(['TStandardCodeTool.AddUnitToUsesSection Replace="',copy(Src,InsertPos,InsertToPos-InsertPos),'" with "',InsertCode,'"']);
  if not SourceChangeCache.Replace(gtNone,gtNone,InsertPos,InsertToPos,
                                   InsertCode) then exit;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.AddUnitToMainUsesSection(const NewUnitName,
  NewUnitInFile: string; SourceChangeCache: TSourceChangeCache;
  const Flags: TAddUsesFlags): boolean;
begin
  Result:=AddUnitToSpecificUsesSection(usMain, NewUnitName, NewUnitInFile,
                                       SourceChangeCache, Flags);
end;

function TStandardCodeTool.AddUnitToImplementationUsesSection(
  const NewUnitName, NewUnitInFile: string;
  SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
begin
  Result:=AddUnitToSpecificUsesSection(usImplementation,
                          NewUnitName, NewUnitInFile, SourceChangeCache, Flags);
end;

function TStandardCodeTool.AddUnitToSpecificUsesSection(
  UsesSection: TUsesSection; const NewUnitName, NewUnitInFile: string;
  SourceChangeCache: TSourceChangeCache; const Flags: TAddUsesFlags): boolean;
var
  UsesNode, OtherUsesNode, SectionNode, Node: TCodeTreeNode;
  NewUsesTerm: string;
  InsertPos: integer;
  Junk: TAtomPosition;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  if not IsDottedIdentifier(NewUnitName) then exit;
  if UsesSection=usMain then begin
    // quick check using only the main uses section
    BuildTree(lsrMainUsesSectionEnd);
    UsesNode:=FindMainUsesNode;
    if (UsesNode<>nil)
    and (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk)) then
      exit(true); // unit already in main uses section
  end;
  if GetSourceType=ctnUnit then
    BuildTree(lsrImplementationUsesSectionEnd)
  else if UsesSection=usImplementation then begin
    MoveCursorToNodeStart(Tree.Root);
    RaiseException(20170421201102,
      ctsCanNotAddAUnitToTheImplementationBecauseOnlyAUnitH);
  end;
  SourceChangeCache.MainScanner:=Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  SourceChangeCache.BeginUpdate;
  try
    UsesNode:=FindMainUsesNode;
    OtherUsesNode:=FindImplementationUsesNode;
    if UsesSection=usImplementation then begin
      SectionNode:=UsesNode;
      UsesNode:=OtherUsesNode;
      OtherUsesNode:=SectionNode;
    end;
    // remove unit from other uses section
    if (OtherUsesNode<>nil) then
      RemoveUnitFromUsesSection(OtherUsesNode,NewUnitName,SourceChangeCache);

    if UsesNode<>nil then begin
      // add unit to existing uses section
      if not (FindUnitInUsesSection(UsesNode,NewUnitName,Junk,Junk))
      then begin
        if not AddUnitToUsesSection(UsesNode,NewUnitName,NewUnitInFile,
                                    SourceChangeCache,Flags)
        then
          exit;
      end;
    end else begin
      // create a new uses section
      if Tree.Root=nil then exit;
      SectionNode:=Tree.Root;
      InsertPos:=0;
      NewUsesTerm:='';
      if SectionNode.Desc=ctnUnit then begin
        // unit
        case UsesSection of
        usMain: SectionNode:=FindInterfaceNode;
        usImplementation: SectionNode:=FindImplementationNode;
        end;
        if SectionNode<>nil then begin
          // add uses to existing interface/implementation before any content
          MoveCursorToNodeStart(SectionNode);
          ReadNextAtom;
          InsertPos := CurPos.EndPos;
        end else begin
          // section is missing => add it
          SectionNode:=Tree.Root;
          case UsesSection of
          usMain: NewUsesTerm:='interface';
          usImplementation: NewUsesTerm:='implementation';
          end;
          NewUsesTerm:=Beauty.BeautifyKeyWord(NewUsesTerm)
                      +Beauty.LineEnd;
          if SectionNode.FirstChild<>nil then begin
            // unit not empty => add in front of first node
            InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.FirstChild.StartPos,
              true);
          end else begin
            // unit empty => add at end
            InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
          end;
        end;
      end;
      if InsertPos<1 then begin
        // not a unit (i.e. program)
        // => insert after title and directives
        Node:=SectionNode.Next;
        if (Node<>nil) and (Node.Desc=ctnSrcName) then
          Node:=Node.NextSkipChilds;
        if Node<>nil then begin
          InsertPos:=FindLineEndOrCodeInFrontOfPosition(Node.StartPos,
            true);
        end else begin
          // program empty => add at end
          InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos,true);
        end;
      end;
      NewUsesTerm:=NewUsesTerm+Beauty.BeautifyKeyWord('uses')+' '+NewUnitName;
      if NewUnitInFile<>'' then
        NewUsesTerm:=NewUsesTerm+' in '''+NewUnitInFile+''';'
      else
        NewUsesTerm:=NewUsesTerm+';';
      if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
        NewUsesTerm) then exit;
    end;
    Result:=true;
  finally
    if not Result then
      SourceChangeCache.Clear;
    if not SourceChangeCache.EndUpdate then
      Result:=false;
  end;
end;

function TStandardCodeTool.UnitExistsInUsesSection(UsesSection: TUsesSection;
  const AnUnitName: string): boolean;
var
  UsesNode: TCodeTreeNode;
begin
  Result:=false;
  if not IsDottedIdentifier(AnUnitName) then
    exit;
  if UsesSection=usMain then
    BuildTree(lsrMainUsesSectionEnd)
  else
    BuildTree(lsrImplementationUsesSectionEnd);
  case UsesSection Of
    usMain: UsesNode:=FindMainUsesNode;
    usImplementation: UsesNode:=FindImplementationUsesNode;
  end;
  Result:=UnitExistsInUsesSection(UsesNode,AnUnitName);
end;

function TStandardCodeTool.UnitExistsInUsesSection(UsesNode: TCodeTreeNode;
  const AnUnitName: string): boolean;
begin
  Result:=false;
  if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
    exit;
  MoveCursorToNodeStart(UsesNode);
  ReadNextAtom; // read 'uses'
  repeat
    ReadNextAtom; // read name
    if not AtomIsIdentifier then exit;
    if ReadAndCompareUsedUnit(AnUnitName) then begin
      // unit found
      exit(true);
    end;
    if UpAtomIs('IN') then begin
      ReadNextAtom;
      ReadNextAtom;
    end;
    if CurPos.Flag=cafSemicolon then break;
    if CurPos.Flag<>cafComma then break;
  until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
end;

function TStandardCodeTool.RemoveUnitFromUsesSection(UsesNode: TCodeTreeNode;
  const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;
var UnitPos, StartPos, EndPos: integer;
  Found: Boolean;
begin
  Result:=false;
  if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName)) then
    exit;
  MoveCursorToNodeStart(UsesNode);
  ReadNextAtom; // read 'uses'
  UnitPos:=0;
  repeat
    EndPos:=CurPos.StartPos;
    ReadNextAtom; // read name
    if not AtomIsIdentifier then exit;
    inc(UnitPos);
    StartPos:=CurPos.StartPos;
    Found:=ReadAndCompareUsedUnit(AnUnitName);
    if UpAtomIs('IN') then begin
      ReadNextAtom;
      ReadNextAtom;
    end;
    if Found then begin
      // unit found
      SourceChangeCache.MainScanner:=Scanner;
      if UnitPos=1 then begin
        // first unit in uses section
        if AtomIsChar(';') then begin
          // last unit in uses section -> delete whole uses section
          StartPos:=FindLineEndOrCodeInFrontOfPosition(UsesNode.StartPos,true,true);
          EndPos:=UsesNode.EndPos;
        end else begin
          // not last unit -> delete with comma behind
          EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
          if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
            StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
        end;
      end else begin
        // not first unit in uses section
        if AtomIsChar(';') then begin
          // last unit -> delete with comma in front
          StartPos:=EndPos;
          EndPos:=CurPos.StartPos;
        end else if PositionsInSameLine(Src,EndPos,StartPos) then begin
          // not first unit in line -> delete with comma in front
          StartPos:=EndPos;
          EndPos:=CurPos.StartPos;
        end else begin
          // first unit in line -> delete with comma behind
          EndPos:=FindLineEndOrCodeAfterPosition(CurPos.EndPos);
          if (EndPos>SrcLen) or (Src[EndPos] in [#10,#13]) then
            StartPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);// delete space in front or even the empty line
        end;
      end;
      if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
        exit;
      if not SourceChangeCache.Apply then exit;
      Result:=true;
      exit;
    end;
    if AtomIsChar(';') then break;
    if not AtomIsChar(',') then break;
  until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
  Result:=true;
end;

function TStandardCodeTool.RemoveUnitFromAllUsesSections(
  const AnUnitName: string; SourceChangeCache: TSourceChangeCache): boolean;

  function RemoveFromSection(UsesNode: TCodeTreeNode): boolean;
  begin
    Result:=(UsesNode=nil)
      or (RemoveUnitFromUsesSection(UsesNode,AnUnitName,SourceChangeCache));
  end;

begin
  Result:=false;
  if (AnUnitName='') or (SourceChangeCache=nil) then exit;
  BuildTree(lsrImplementationUsesSectionEnd);

  SourceChangeCache.BeginUpdate;
  try
    if not RemoveFromSection(FindMainUsesNode) then exit;
    if not RemoveFromSection(FindImplementationUsesNode) then exit;
  finally
    Result:=SourceChangeCache.EndUpdate;
  end;
end;

function TStandardCodeTool.FixUsedUnitCase(
  SourceChangeCache: TSourceChangeCache): boolean;

  function FixUsesSection(UsesNode: TCodeTreeNode): boolean;
  begin
    Result:=(UsesNode=nil) or FixUsedUnitCaseInUsesSection(UsesNode,SourceChangeCache);
  end;

begin
  //debugln('TStandardCodeTool.FixUsedUnitCase ',MainFilename);
  Result:=false;
  BuildTree(lsrImplementationUsesSectionEnd);
  if not FixUsesSection(FindMainUsesNode) then exit;
  if not FixUsesSection(FindImplementationUsesNode) then exit;
  Result:=true;
end;

function TStandardCodeTool.FixUsedUnitCaseInUsesSection(
  UsesNode: TCodeTreeNode; SourceChangeCache: TSourceChangeCache): boolean;
  
  function FindUnit(AFilename: string): string;
  var
    CurDir: String;
    MakeRelative: Boolean;
  begin
    Result:='';
    AFilename:=TrimFilename(AFilename);
    CurDir:='';
    if FilenameIsAbsolute(AFilename) then begin
      MakeRelative:=false;
    end else begin
      MakeRelative:=true;
      if TCodeBuffer(Scanner.MainCode).IsVirtual then exit;
      CurDir:=ExtractFilePath(TCodeBuffer(Scanner.MainCode).Filename);
      AFilename:=CurDir+AFilename;
    end;
    Result:=DirectoryCache.Pool.FindDiskFilename(AFilename,true);
    if Result='' then exit;
    if MakeRelative then
      Result:=CreateRelativePath(Result,CurDir);
  end;
  
var
  UnitInFilename: String;
  Changed: Boolean;
  RealUnitInFilename: String;
  UnitNameRange: TAtomPosition;
  InAtom: TAtomPosition;
begin
  Result:=false;
  if (UsesNode=nil) then exit;
  MoveCursorToNodeStart(UsesNode);
  ReadNextAtom; // read 'uses'
  Changed:=false;
  repeat
    ReadNextAtom; // read name
    if not ReadNextUsedUnit(UnitNameRange,InAtom,false) then exit;
    if InAtom.StartPos>1 then begin
      UnitInFilename:=GetAtom(InAtom);
      //debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection A UnitInFilename="',UnitInFilename,'"');
      if (UnitInFilename<>'') and (UnitInFilename[1]='''') then begin
        UnitInFilename:=copy(UnitInFilename,2,length(UnitInFilename)-2);
        RealUnitInFilename:=FindUnit(UnitInFilename);
        //debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection B RealUnitInFilename="',RealUnitInFilename,'"');
        if (RealUnitInFilename<>'')
        and (RealUnitInFilename<>UnitInFilename) then begin
          if not Changed then begin
            SourceChangeCache.MainScanner:=Scanner;
            Changed:=true;
          end;
          debugln('TStandardCodeTool.FixUsedUnitCaseInUsesSection Replacing UnitInFilename="',UnitInFilename,'" with "',RealUnitInFilename,'"');
          if not SourceChangeCache.Replace(gtNone,gtNone,
            InAtom.StartPos,InAtom.EndPos,''''+RealUnitInFilename+'''') then exit;
        end;
      end;
    end;
    if CurPos.Flag=cafSemicolon then break;
    if CurPos.Flag<>cafComma then exit;
  until (CurPos.StartPos>UsesNode.EndPos) or (CurPos.StartPos>SrcLen);
  if Changed and (not SourceChangeCache.Apply) then exit;
  Result:=true;
end;

function TStandardCodeTool.FindUsedUnitNames(var MainUsesSection,
  ImplementationUsesSection: TStrings): boolean;
var
  MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
begin
  MainUsesSection:=nil;
  ImplementationUsesSection:=nil;
  // find the uses sections
  BuildTree(lsrImplementationUsesSectionEnd);
  MainUsesNode:=FindMainUsesNode;
  ImplementatioUsesNode:=FindImplementationUsesNode;
  // create lists
  try
    MainUsesSection:=UsesSectionToUnitNames(MainUsesNode);
    ImplementationUsesSection:=UsesSectionToUnitNames(ImplementatioUsesNode);
  except
    FreeAndNil(MainUsesSection);
    FreeAndNil(ImplementationUsesSection);
    raise;
  end;
  Result:=true;
end;

function TStandardCodeTool.FindUsedUnitNames(var List: TStringToStringTree
  ): boolean;
  
  procedure Collect(UsesNode: TCodeTreeNode; const Tag: string);
  var
    UnitNameAtom: TAtomPosition;
    InAtom: TAtomPosition;
    OldTag: string;
    AnUnitName: String;
  begin
    if UsesNode=nil then exit;
    MoveCursorToUsesStart(UsesNode);
    repeat
      // read next unit name
      ReadNextUsedUnit(UnitNameAtom, InAtom);
      AnUnitName:=GetAtom(UnitNameAtom);
      // tag unit in list
      OldTag:=List[AnUnitName];
      if System.Pos(Tag,OldTag)<1 then
        List[AnUnitName]:=OldTag+Tag;
      if CurPos.Flag=cafComma then begin
        // read next unit name
        ReadNextAtom;
      end else if CurPos.Flag=cafSemicolon then begin
        break;
      end else
        RaiseExceptionFmt(20170421201120,ctsStrExpectedButAtomFound,[';',GetAtom]);
    until false;
    Result:=true;
  end;
  
begin
  // find the uses sections
  List:=TStringToStringTree.Create(false);
  BuildTree(lsrImplementationUsesSectionEnd);
  Collect(FindMainUsesNode,'Main');
  Collect(FindImplementationUsesNode,'Implementation');
  Result:=true;
end;

function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection: TStrings
  ): boolean;
var
  MainUsesNode: TCodeTreeNode;
begin
  MainUsesSection:=nil;
  // find the uses sections
  BuildTree(lsrMainUsesSectionEnd);
  MainUsesNode:=FindMainUsesNode;
  // create lists
  try
    MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
  except
    FreeAndNil(MainUsesSection);
    raise;
  end;
  Result:=true;
end;

function TStandardCodeTool.FindUsedUnitFiles(var MainUsesSection,
  ImplementationUsesSection: TStrings): boolean;
var
  MainUsesNode, ImplementatioUsesNode: TCodeTreeNode;
begin
  MainUsesSection:=nil;
  ImplementationUsesSection:=nil;
  // find the uses sections
  BuildTree(lsrImplementationUsesSectionEnd);
  MainUsesNode:=FindMainUsesNode;
  ImplementatioUsesNode:=FindImplementationUsesNode;
  // create lists
  try
    MainUsesSection:=UsesSectionToFilenames(MainUsesNode);
    ImplementationUsesSection:=UsesSectionToFilenames(ImplementatioUsesNode);
  except
    FreeAndNil(MainUsesSection);
    FreeAndNil(ImplementationUsesSection);
    raise;
  end;
  Result:=true;
end;

{------------------------------------------------------------------------------
  function TStandardCodeTool.FindDelphiProjectUnits(var FoundInUnits,
    MissingInUnits, NormalUnits: TStrings): boolean;

  Reads the main uses section and tries to find each unit file having
  an 'in' modifier.
  The associated objects in the list will be the found codebuffers.
  FoundInUnits returns the list of found 'in' unitnames plus TCodeBuffer
  MissingInUnits returns the list of missing 'in' unitnames
  NormalUnits returns the list of unitnames plus TCodeBuffer (if found)

  If no codebuffer was found/created then the filename will be the unit name
  plus the 'in' extension.
------------------------------------------------------------------------------}
function TStandardCodeTool.FindDelphiProjectUnits(out FoundInUnits,
  MissingInUnits, NormalUnits: TStrings; UseContainsSection: boolean;
  IgnoreNormalUnits: boolean): boolean;
var
  AnUnitName, AnUnitInFilename: string;
  NewCode: TCodeBuffer;
  UsesNode: TCodeTreeNode;
  Node: TCodeTreeNode;
begin
  Result:=false;
  FoundInUnits:=nil;
  MissingInUnits:=nil;
  NormalUnits:=nil;
  // find the uses sections
  BuildTree(lsrMainUsesSectionEnd);
  UsesNode:=FindMainUsesNode(UseContainsSection);
  if UsesNode=nil then exit;
  FoundInUnits:=TStringListUTF8Fast.Create;
  MissingInUnits:=TStringListUTF8Fast.Create;
  if IgnoreNormalUnits then
    NormalUnits:=nil
  else
    NormalUnits:=TStringListUTF8Fast.Create;
  Node:=UsesNode.FirstChild;
  while Node<>nil do begin
    // read next unit name
    AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
    // find unit file
    if AnUnitInFilename<>'' then begin
      // An 'in' unit => Delphi project file
      NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
      if (NewCode=nil) then begin
        // no source found
        MissingInUnits.Add(AnUnitName+' in '+AnUnitInFilename);
      end else begin
        // source found => add filename to list
        FoundInUnits.AddObject(AnUnitName+' in '+AnUnitInFilename,NewCode);
      end;
    end else if AnUnitName<>'' then begin
      // the units without 'in' are 'Forms' or units added by the user
      if not IgnoreNormalUnits then begin
        NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
        NormalUnits.AddObject(AnUnitName,NewCode);
      end;
    end;
    Node:=Node.NextBrother;
  end;
  Result:=true;
end;

{------------------------------------------------------------------------------
  function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
    ): TStrings;

  Reads the uses section backwards and tries to find each unit file.
  The associated objects in the list will be the found codebuffers.
  If no codebuffer was found/created then the filename will be the unit name
  plus the 'in' extension.
------------------------------------------------------------------------------}
function TStandardCodeTool.UsesSectionToFilenames(UsesNode: TCodeTreeNode
  ): TStrings;
var
  AnUnitName, AnUnitInFilename: string;
  NewCode: TCodeBuffer;
  UnitFilename: string;
  Node: TCodeTreeNode;
begin
  Result:=TStringList.Create;
  if UsesNode=nil then exit;
  Node:=UsesNode.LastChild;
  while Node<>nil do begin
    // read unit name
    AnUnitName:=ExtractUsedUnitName(Node,@AnUnitInFilename);
    if AnUnitName<>'' then begin
      // find unit file
      NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,false,Node.StartPos);
      if (NewCode=nil) then begin
        // no source found
        UnitFilename:=AnUnitName;
        if AnUnitInFilename<>'' then
          UnitFilename:=UnitFilename+' in '+AnUnitInFilename;
      end else begin
        // source found
        UnitFilename:=NewCode.Filename;
      end;
      // add filename to list
      Result.AddObject(UnitFilename,NewCode);
    end;
    Node:=Node.PriorBrother;
  end;
end;

function TStandardCodeTool.UsesSectionToUnitnames(UsesNode: TCodeTreeNode
  ): TStrings;
var
  AnUnitName: string;
  Node: TCodeTreeNode;
begin
  Result:=TStringList.Create;
  if UsesNode=nil then exit;
  Node:=UsesNode.LastChild;
  while Node<>nil do begin
    // read unit name
    AnUnitName:=ExtractUsedUnitName(Node);
    if AnUnitName<>'' then
      Result.Add(AnUnitName);
    Node:=Node.PriorBrother;
  end;
end;

function TStandardCodeTool.FindMissingUnits(var MissingUnits: TStrings;
  FixCase: boolean; SearchImplementation: boolean;
  SourceChangeCache: TSourceChangeCache): boolean;
const
  FPCSrcSearchRequiresPPU = true;

  function CheckUsesSection(UsesNode: TCodeTreeNode): boolean;
  var
    OldUnitName: String;
    OldInFilename: String;
    AFilename: String;
    s: String;
    NewUnitName: String;
    NewInFilename: String;
    FromPos: LongInt;
    ToPos: LongInt;
    Node: TCodeTreeNode;
  begin
    if UsesNode=nil then exit(true);

    Node:=UsesNode.FirstChild;
    while Node<>nil do begin
      // read next unit name
      OldUnitName:=ExtractUsedUnitName(Node,@OldInFilename);
      // find unit file
      NewUnitName:=OldUnitName;
      NewInFilename:=OldInFilename;
      //debugln(['CheckUsesSection NewUnitName="',NewUnitName,'" NewInFilename="',NewInFilename,'"']);
      AFilename:=DirectoryCache.FindUnitSourceInCompletePath(
         NewUnitName,NewInFilename,true,FPCSrcSearchRequiresPPU,AddedNameSpace);
      s:=NewUnitName;
      if NewInFilename<>'' then
        s:=s+' in '''+NewInFilename+'''';
      if AFilename<>'' then begin
        // unit found
        if FixCase
        and ((NewUnitName<>OldUnitName) or (NewInFilename<>OldInFilename)) then
        begin
          // fix case
          FromPos:=Node.StartPos;
          ToPos:=Node.EndPos;
          SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,s);
          DebugLn('TStandardCodeTool.FindMissingUnits.CheckUsesSection fix case Unit Name(',OldUnitName,'->',NewUnitName,') InFile(',OldInFilename,'->',NewInFilename,')');
        end;
      end else begin
        // unit not found
        if MissingUnits=nil then MissingUnits:=TStringList.Create;
        MissingUnits.Add(s);
      end;
      Node:=Node.NextBrother;
    end;
    Result:=true;
  end;
  
begin
  Result:=false;
  BuildTree(lsrImplementationUsesSectionEnd);
  if FixCase then
    SourceChangeCache.MainScanner:=Scanner;
  try
    if not CheckUsesSection(FindMainUsesNode) then exit;
    if SearchImplementation
    and not CheckUsesSection(FindImplementationUsesNode) then exit;
  except
    FreeAndNil(MissingUnits);
    raise;
  end;
  if FixCase then
    Result:=SourceChangeCache.Apply
  else
    Result:=true;
end;

function TStandardCodeTool.CommentUnitsInUsesSection(MissingUnits: TStrings;
  SourceChangeCache: TSourceChangeCache; UsesNode: TCodeTreeNode): boolean;
// Examples:
// 1. uses {a,} b, c;    commenting one unit not at end
// 2. uses a, {b,} c;    commenting one unit not at end
// 3. uses {a, b,} c;    commenting several units not at end
// 4. uses a{, b, c} ;   commenting units at end
// 5. {uses a, b, c;}    commenting all units
// 6. uses {a,} b{, c};  commenting several units

  procedure Comment(StartPos, EndPos: integer);
  begin
    //debugln(['Comment ',dbgstr(copy(Src,StartPos,EndPos-StartPos))]);
    CommentCode(StartPos,EndPos,SourceChangeCache,false);
  end;

var
  i: Integer;
  CurUnitName: String;
  CommentCurUnit: Boolean;
  FirstCommentUnitStart: Integer;
  LastCommaAfterCommentUnitsStart: Integer;
  LastNormalUnitEnd: Integer;
  LastCommentUnitEnd: Integer;
  Node: TCodeTreeNode;
begin
  Result:=true;
  if UsesNode=nil then exit;
  FirstCommentUnitStart:=-1;
  LastCommaAfterCommentUnitsStart:=-1;
  LastNormalUnitEnd:=-1;
  LastCommentUnitEnd:=-1;
  Node:=UsesNode.FirstChild;
  while Node<>nil do begin
    // check if unit should be commented
    CurUnitName:=ExtractUsedUnitName(Node);
    // Note: CurPos is now on atom behind used unit, i.e. comma or semicolon
    i:=MissingUnits.Count-1;
    while (i>=0)
    and (CompareIdentifiers(PChar(Pointer(MissingUnits[i])),
                            PChar(Pointer(CurUnitName)))<>0) do
      dec(i);
    CommentCurUnit:=i>=0;
    //debugln('CommentUnitsInUsesSection CurUnitName="',CurUnitName,'" CommentCurUnit=',dbgs(CommentCurUnit));

    if CommentCurUnit then begin
      // unit should be commented
      if FirstCommentUnitStart<1 then FirstCommentUnitStart:=Node.StartPos;
      LastCommentUnitEnd:=Node.EndPos;
    end else begin
      // unit should be kept
      LastNormalUnitEnd:=Node.EndPos;
      if FirstCommentUnitStart>=1 then begin
        // there are some units to be commented
        // See examples: 1., 2., 3. and 6.
        Comment(FirstCommentUnitStart,LastCommaAfterCommentUnitsStart);
        FirstCommentUnitStart:=-1;
        LastCommentUnitEnd:=-1;
        LastCommaAfterCommentUnitsStart:=-1;
      end;
    end;

    if CommentCurUnit then
      LastCommaAfterCommentUnitsStart:=CurPos.EndPos;

    if CurPos.Flag<>cafComma then begin
      if CommentCurUnit then begin
        // last unit must be commented
        if LastNormalUnitEnd>=1 then begin
          // comment last unit and keep some units in front
          // See example: 4.
          Comment(LastNormalUnitEnd,LastCommentUnitEnd);
        end else begin
          // all units should be commented
          // See example: 5.
          Comment(UsesNode.StartPos,CurPos.EndPos);
        end;
      end;
      break;
    end;

    Node:=Node.NextBrother;
  end;
end;

function TStandardCodeTool.CommentUnitsInUsesSections(MissingUnits: TStrings;
  SourceChangeCache: TSourceChangeCache): boolean;
begin
  if (MissingUnits=nil) or (MissingUnits.Count=0) then
    exit(true);
  Result:=false;
  BuildTree(lsrInitializationStart);
  SourceChangeCache.MainScanner:=Scanner;
  if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindMainUsesNode) then exit;
  if not CommentUnitsInUsesSection(MissingUnits, SourceChangeCache, FindImplementationUsesNode) then exit;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.FindUnusedUnits(Units: TStrings): boolean;
// returns a list of unitname=flags
// flags are a comma separated list of words:
//   'implementation': unit is in implementation uses section
//   'used': an identifier of the interface is used
//   'code': unit has non empty initialization/finalization section
var
  Identifiers: TAVLTree;// all identifiers used in this unit

  function IsUnitAlreadyChecked(const AnUnitName: string): boolean;
  var
    i: Integer;
  begin
    for i:=0 to Units.Count-1 do
      if SysUtils.CompareText(Units.Names[i],AnUnitName)=0 then exit(true);
    Result:=false;
  end;

  procedure GatherIdentifiersInRange(StartPos, EndPos: integer);
  // gather all used identifiers from this unit in the range Startpos..EndPos
  var
    Identifier: PChar;
  begin
    if (StartPos<1) or (StartPos>=EndPos) then exit;
    MoveCursorToCleanPos(StartPos);
    repeat
      ReadNextAtom;
      if CurPos.StartPos>=EndPos then break;
      if IsIdentStartChar[Src[CurPos.StartPos]] then begin
        Identifier:=@Src[CurPos.StartPos];
        if Identifiers.Find(Identifier)=nil then begin
          {$IFDEF VerboseFindUnusedUnits}
          DebugLn(['Used Identifier=',GetIdentifier(Identifier)]);
          {$ENDIF}
          Identifiers.Add(Identifier);
        end;
      end;
    until false;
  end;

  procedure GatherIdentifiers;
  // gather all used identifiers from this unit
  var
    StartPos: Integer;

    procedure Gather(EndPos: integer);
    begin
      if StartPos<1 then exit;
      GatherIdentifiersInRange(StartPos,EndPos);
      StartPos:=-1;
    end;

  var
    Node: TCodeTreeNode;
  begin
    if Identifiers<>nil then exit;
    Identifiers:=TAVLTree.Create(@CompareIdentifierPtrs);
    {$IFDEF VerboseFindUnusedUnits}
    DebugLn(['GatherIdentifiers ']);
    {$ENDIF}
    StartPos:=-1;
    Node:=Tree.Root;
    while Node<>nil do begin
      case Node.Desc of
      ctnUseUnit,ctnUsesSection,ctnUseUnitNamespace,ctnUseUnitClearName,
      ctnProgram,ctnUnit,ctnPackage,ctnLibrary,ctnEndPoint:
        begin
          // skip node
          Gather(Node.StartPos);
        end;
      ctnEnumIdentifier,
      ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition:
        begin
          // start reading behind identifier
          Gather(Node.StartPos);
          MoveCursorToCleanPos(Node.StartPos);
          ReadNextAtom;
          StartPos:=CurPos.EndPos;
        end;
      else
        if StartPos<1 then
          StartPos:=Node.StartPos;
      end;
      Node:=Node.Next;
    end;
  end;

  function InterfaceIsUsed(Tool: TFindDeclarationTool;
    IntfNode: TCodeTreeNode): boolean;

    function IsIdentifierUsed(StartPos: integer): boolean;
    begin
      {$IFDEF VerboseFindUnusedUnits}
      if CompareIdentifiers(PChar(GetIdentifier(@Tool.Src[StartPos])),'TComponent')=0 then
        DebugLn(['IsIdentifierUsed ',GetIdentifier(@Tool.Src[StartPos])]);
      {$ENDIF}
      Result:=Identifiers.Find(@Tool.Src[StartPos])<>nil;
    end;

    function IsNodeVisible(Node: TCodeTreeNode): boolean;
    begin
      Result:=false;
      Node:=Node.Parent;
      while Node<>nil do begin
        if Node.Desc=ctnInterface then exit(true);
        if Node.Desc in AllClasses then exit;
        Node:=Node.Parent;
      end;
    end;

    function FindProceduresInHelper(Node: TCodeTreeNode): boolean;
    begin
      Result := False;
      Node := Node.FirstChild;
      while (Node<>nil) and (Node.Desc <> ctnEndPoint) do
      begin
        if (Node.Desc = ctnProcedure) and (Node.FirstChild<>nil) then
        begin
          if IsIdentifierUsed(Node.FirstChild.StartPos) then
            Exit(True);
        end;
        Node:=Node.Next;
      end;
    end;

  var
    Node: TCodeTreeNode;
  begin
    Result:=true;
    Node:=IntfNode.FirstChild;
    while Node<>nil do begin
      case Node.Desc of
      ctnTypeHelper, ctnRecordHelper, ctnClassHelper:
        if FindProceduresInHelper(Node) then exit;
      ctnEnumIdentifier:
        if IsIdentifierUsed(Node.StartPos) then exit;
      ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnGenericName:
        if IsNodeVisible(Node) and IsIdentifierUsed(Node.StartPos) then exit;
      ctnProcedure:
        if (Node.Parent.Desc=ctnInterface)
        and (Node.FirstChild<>nil)
        and (Node.FirstChild.Desc=ctnProcedureHead)
        and IsIdentifierUsed(Node.FirstChild.StartPos) then exit;
      ctnGlobalProperty:
        if Tool.MoveCursorToPropName(Node)
        and IsIdentifierUsed(Tool.CurPos.StartPos) then exit;
      end;
      Node:=Node.Next;
    end;
    Result:=false;
  end;

  procedure CheckUnit(Tool: TFindDeclarationTool;
    out HasCode, UseInterface: boolean);
  var
    Node: TCodeTreeNode;
    Identifier: String;
  begin
    GatherIdentifiers;
    HasCode:=false;
    UseInterface:=false;
    // parse used unit
    Tool.BuildTree(lsrEnd);
    Node:=Tool.Tree.Root;
    while (Node<>nil) do begin
      case Node.Desc of
      ctnUnit,ctnPackage,ctnLibrary:
        begin
          Identifier:=Tool.ExtractSourceName;
          if Identifiers.Find(PChar(Identifier))<>nil then
            UseInterface:=true;
        end;
      ctnInterface:
        if not UseInterface then
          UseInterface:=InterfaceIsUsed(Tool,Node);
      ctnInitialization,ctnFinalization,ctnBeginBlock:
        begin
          HasCode:=true;
          break;
        end;
      end;
      Node:=Node.NextBrother;
    end;
  end;

  procedure CheckUsesSection(UsesNode: TCodeTreeNode; InImplementation: boolean);
  var
    Unit_Name: String;
    UnitInFilename: String;
    Tool: TFindDeclarationTool;
    HasCode: boolean;
    UseInterface: boolean;
    Flags: String;
    Node: TCodeTreeNode;
  begin
    HasCode:=false;
    UseInterface:=false;
    if UsesNode=nil then exit;
    Node:=UsesNode.FirstChild;
    while Node<>nil do begin
      Unit_Name:=ExtractUsedUnitName(Node,@UnitInFilename);
      if not IsUnitAlreadyChecked(Unit_Name) then begin
        // try to load the used unit
        {$IFDEF VerboseFindUnusedUnits}
        DebugLn(['CheckUsesSection ',Unit_Name,' in ',UnitInFilename]);
        {$ENDIF}
        Tool:=FindCodeToolForUsedUnit(Unit_Name,UnitInFilename,true);
        // parse the used unit
        CheckUnit(Tool,HasCode,UseInterface);
        Flags:='';
        if InImplementation then
          Flags:=Flags+',implementation';
        if HasCode then
          Flags:=Flags+',code';
        if UseInterface then
          Flags:=Flags+',used';
        {$IFDEF VerboseFindUnusedUnits}
        DebugLn(['CheckUsesSection ',Unit_Name,'=',Flags]);
        {$ENDIF}
        Units.Add(Unit_Name+'='+Flags);
      end;
      Node:=Node.NextBrother;
    end;
  end;

begin
  Result:=false;
  {$IFDEF VerboseFindUnusedUnits}
  DebugLn(['TStandardCodeTool.FindUnusedUnits START']);
  {$ENDIF}
  BuildTree(lsrEnd);
  Identifiers:=nil;
  try
    CheckUsesSection(FindMainUsesNode,false);
    CheckUsesSection(FindImplementationUsesNode,true);
  finally
    Identifiers.Free;
  end;
  {$IFDEF VerboseFindUnusedUnits}
  DebugLn(['TStandardCodeTool.FindUnusedUnits END']);
  {$ENDIF}
  Result:=true;
end;

function TStandardCodeTool.FindNextIncludeInInitialization(
  var LinkIndex: integer): TCodeBuffer;
// LinkIndex < 0  ->  search first
var
  InitializationNode: TCodeTreeNode;
  StartCode: TCodeBuffer;
begin
  Result:=nil;
  if LinkIndex<0 then begin
    BuildTree(lsrEnd);
    InitializationNode:=FindInitializationNode;
    if InitializationNode=nil then exit;
    LinkIndex:=Scanner.LinkIndexAtCleanPos(InitializationNode.StartPos);
  end else begin
    InitializationNode:=nil;
    inc(LinkIndex);
  end;
  if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) then exit;
  StartCode:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
  // ToDo: Fix the test for InitializationNode, it can be Nil.
  while (LinkIndex<Scanner.LinkCount)
  and (Scanner.Links[LinkIndex].CleanedPos<InitializationNode.EndPos) do begin
    Result:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
    if (Result<>StartCode) then
      exit;
    inc(LinkIndex);
  end;
  Result:=nil;
end;

function TStandardCodeTool.FindLazarusResourceInBuffer(
  ResourceCode: TCodeBuffer; const ResourceName: string): TAtomPosition;
var ResNameCode: string;

  function ReadLazResource: boolean;
  begin
    Result:=false;
    if not ReadNextAtomIsChar('.') then exit;
    if not ReadNextUpAtomIs('ADD') then exit;
    if not ReadNextAtomIsChar('(') then exit;
    ReadNextAtom;
    if not AtomIsStringConstant then exit;
    if UpAtomIs(ResNameCode) then
      Result:=true;
    repeat
      ReadNextAtom;
    until (CurPos.StartPos>SrcLen) or (AtomIsChar(')'));
    ReadNextAtom; // read ';'
  end;
  
var CleanPos, MaxCleanPos: integer;
begin
  Result.StartPos:=-1;
  if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255) then
    exit;
  if Scanner.CursorToCleanPos(1,ResourceCode,CleanPos)<>0 then exit;
  if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
    MaxCleanPos)<>0 then
    MaxCleanPos:=-1;
  MoveCursorToCleanPos(CleanPos);
  ResNameCode:=''''+UpperCaseStr(ResourceName)+'''';
  // search "LazarusResources.Add('<ResourceName>',"
  repeat
    ReadNextAtom; // read 'LazarusResources'
    if UpAtomIs('LAZARUSRESOURCES') then begin
      Result.StartPos:=CurPos.StartPos;
      if ReadLazResource then begin
        Result.EndPos:=CurPos.EndPos;
        exit;
      end;
    end;
  until (CurPos.StartPos>SrcLen) or UpAtomIs('END')
  or ((MaxCleanPos>0) and (CurPos.StartPos>MaxCleanPos));
  Result.StartPos:=-1;
end;

function TStandardCodeTool.FindLazarusResource(
  const ResourceName: string): TAtomPosition;
// search Resource in all include files
var LinkIndex: integer;
  CurCode: TCodeBuffer;
begin
  Result.StartPos:=-1;
  Result.EndPos:=-1;
  Result.Flag:=cafNone;
  LinkIndex:=-1;
  CurCode:=FindNextIncludeInInitialization(LinkIndex);
  while (CurCode<>nil) do begin
    Result:=FindLazarusResourceInBuffer(CurCode,ResourceName);
    if Result.StartPos>0 then exit;
    CurCode:=FindNextIncludeInInitialization(LinkIndex);
  end;
end;

function TStandardCodeTool.AddLazarusResource(ResourceCode: TCodeBuffer;
  const ResourceName, ResourceData: string;
  SourceChangeCache: TSourceChangeCache): boolean;
// ResoureData is the complete LazarusResource Statement
var FromPos, ToPos, i: integer;
  OldPosition: TAtomPosition;
begin
  Result:=false;
  if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
  or (ResourceData='') or (SourceChangeCache=nil) then exit;
  BuildTree(lsrEnd);
  SourceChangeCache.MainScanner:=Scanner;
  OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
  if OldPosition.StartPos>0 then begin
    // replace old resource
    FromPos:=OldPosition.StartPos;
    ToPos:=OldPosition.EndPos;
    if not SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
      ResourceData) then exit;
  end else begin
    // insert new resource
    if ResourceCode.SourceLength>0 then begin
      if Scanner.CursorToCleanPos(ResourceCode.SourceLength,ResourceCode,
        FromPos)<>0 then exit;
      inc(FromPos);
    end else begin
      // resource code empty -> can not be found in cleaned code
      // special replace
      i:=0;
      while (i<Scanner.LinkCount) 
      and (Scanner.Links[i].Code<>ResourceCode) do
        inc(i);
      if i>=Scanner.LinkCount then exit;
      FromPos:=Scanner.Links[i].CleanedPos;
    end;
    if not SourceChangeCache.ReplaceEx(gtNewLine,gtNewLine,FromPos,FromPos,
      ResourceCode,ResourceCode.SourceLength+1,ResourceCode.SourceLength+1,
      ResourceData)
    then exit;
  end;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.RemoveLazarusResource(ResourceCode: TCodeBuffer;
  const ResourceName: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var OldPosition: TAtomPosition;
begin
  Result:=false;
  if (ResourceCode=nil) or (ResourceName='') or (length(ResourceName)>255)
  or (SourceChangeCache=nil) then exit;
  BuildTree(lsrEnd);
  SourceChangeCache.MainScanner:=Scanner;
  OldPosition:=FindLazarusResourceInBuffer(ResourceCode,ResourceName);
  if OldPosition.StartPos>0 then begin
    OldPosition.StartPos:=FindLineEndOrCodeInFrontOfPosition(
         OldPosition.StartPos);
    OldPosition.EndPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
    if not SourceChangeCache.Replace(gtNone,gtNone,
      OldPosition.StartPos,OldPosition.EndPos,'') then exit;
  end;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.RenameInclude(LinkIndex: integer;
  const NewFilename: string; KeepPath: boolean;
  SourceChangeCache: TSourceChangeCache): boolean;
{ change filename in an include directive
  if KeepPath is true and the include dircetive contains a path
  (relative or absolute), then this path is kept and only the filename is
  replaced
}
var IncludeStart, IncludeEnd, FileStart, FileNameStart, FileEnd: integer;
begin
  Result:=false;
  if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount) or (NewFileName='')
  or (KeepPath and (length(NewFilename)>255))
  or (SourceChangeCache=nil) then exit;
  // find include directive
  IncludeEnd:=Scanner.Links[LinkIndex].CleanedPos;
  IncludeStart:=IncludeEnd-1;
  if IncludeStart<1 then exit;
  FileEnd:=0;
  case Src[IncludeStart] of
    '}':
      begin
        FileEnd:=IncludeStart;
        dec(IncludeStart);
        while (IncludeStart>0) and (Src[IncludeStart]<>'{') do
          dec(IncludeStart);
      end;
    ')':
      begin
        dec(IncludeStart);
        FileEnd:=IncludeStart;
        while (IncludeStart>1)
        and ((Src[IncludeStart]<>'*') or (Src[IncludeStart-1]<>'(')) do
          dec(IncludeStart);
      end;
    #13,#10:
      begin
        FileEnd:=IncludeStart;
        if (FileEnd>0) and (IsLineEndChar[Src[FileEnd]]) then dec(FileEnd);
        dec(IncludeStart);
        while (IncludeStart>1)
        and ((Src[IncludeStart]<>'/') or (Src[IncludeStart-1]<>'/')) do
          dec(IncludeStart);
      end;
  end;
  if IncludeStart<1 then exit;
  FileStart:=IncludeStart;
  while (FileStart<IncludeEnd) and (Src[FileStart]<>'$') do
    inc(FileStart);
  while (FileStart<IncludeEnd) and (not (IsSpaceChar[Src[FileStart]])) do
    inc(FileStart);
  while (FileStart<IncludeEnd) and (IsSpaceChar[Src[FileStart]]) do
    inc(FileStart);
  if FileStart>=IncludeEnd then exit;
  SourceChangeCache.MainScanner:=Scanner;
  if KeepPath then begin
    FileNameStart:=FileEnd;
    while (FileNameStart>FileStart) and (Src[FileNameStart]<>PathDelim) do
      dec(FileNameStart);
    if Src[FileNameStart]=PathDelim then
      FileStart:=FileNameStart+1;
  end;
  if not SourceChangeCache.Replace(gtNone,GtNone,FileStart,FileEnd,
    NewFilename) then exit;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.CheckLFM(LFMBuf: TCodeBuffer; out LFMTree: TLFMTree;
  const OnFindDefineProperty: TOnFindDefinePropertyForContext;
  RootMustBeClassInUnit: boolean; RootMustBeClassInIntf: boolean;
  ObjectsMustExist: boolean): boolean;
var
  RootContext: TFindContext;

  function CreateFootNote(const Context: TFindContext): string;
  var
    Caret: TCodeXYPosition;
  begin
    Result:='. See '+Context.Tool.MainFilename;
    if Context.Tool.CleanPosToCaret(Context.Node.StartPos,Caret) then
      Result:=Result+'('+IntToStr(Caret.Y)+','+IntToStr(Caret.X)+')';
  end;

  function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
    const ClassContext: TFindContext; ContextIsDefault: boolean): boolean; forward;

  function FindNonPublishedDefineProperty(LFMNode: TLFMTreeNode;
    DefaultErrorPosition: integer;
    const IdentName: string; const ClassContext: TFindContext): boolean;
  // properties can be defined via DefineProperties
  var
    PropertyNode: TLFMPropertyNode;
    ObjectNode: TLFMObjectNode;
    AncestorClassContext: TFindContext;
    Params: TFindDeclarationParams;
    IsDefined: Boolean;
  begin
    Result:=false;
    if (not (LFMNode is TLFMPropertyNode)) then exit;
    PropertyNode:=TLFMPropertyNode(LFMNode);
    if (PropertyNode.Parent=nil)
    or (not (PropertyNode.Parent is TLFMObjectNode)) then exit;
    ObjectNode:=TLFMObjectNode(PropertyNode.Parent);
    // find define property
    IsDefined:=false;
    if Assigned(OnFindDefineProperty) then begin
      AncestorClassContext:=CleanFindContext;
      if ClassContext.Tool=Self then begin
        // the class is defined in this source
        // -> try to find the ancestor class
        if ObjectNode.AncestorContextValid then begin
          AncestorClassContext:=CreateFindContext(
                                  TFindDeclarationTool(ObjectNode.AncestorTool),
                                  TCodeTreeNode(ObjectNode.AncestorNode));
        end else begin
          {$IFDEF VerboseCheckLFM}
          debugln('FindNonPublishedDefineProperty Class is defined in this source: search ancestor ... ');
          {$ENDIF}
          Params:=TFindDeclarationParams.Create;
          try
            Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
                           fdfExceptionOnPredefinedIdent];
            Params.ContextNode:=ClassContext.Node;
            try
              if ClassContext.Tool.FindAncestorOfClass(ClassContext.Node,
                Params,true) then
              begin
                {$IFDEF VerboseCheckLFM}
                debugln('FindNonPublishedDefineProperty Ancestor found');
                {$ENDIF}
                AncestorClassContext:=CreateFindContext(Params);
                ObjectNode.AncestorTool:=AncestorClassContext.Tool;
                ObjectNode.AncestorNode:=AncestorClassContext.Node;
              end;
            except
              // ignore search/parse errors
              on E: ECodeToolError do ;
            end;
          finally
            Params.Free;
          end;
          ObjectNode.AncestorContextValid:=true;
        end;
      end;
      OnFindDefineProperty(Self,ClassContext,AncestorClassContext,LFMNode,
        IdentName,IsDefined);
      if IsDefined then begin
        //debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' IdentName="',IdentName,'"');
      end else begin
        {$IFDEF VerboseCheckLFM}
        debugln('FindNonPublishedDefineProperty Path=',LFMNode.GetPath,' NO DEFINE PROPERTIES');
        {$ENDIF}
        if DefaultErrorPosition=0 then ;
      end;
    end;
    Result:=IsDefined;
  end;

  function FindLFMIdentifier(LFMNode: TLFMTreeNode;
    DefaultErrorPosition: integer;
    const IdentName: string; const ClassContext: TFindContext;
    SearchInDefinePropertiesToo, ErrorOnNotFound: boolean;
    out IdentContext: TFindContext): boolean;
  var
    Params: TFindDeclarationParams;
    IsPublished, IsMissingInCode: Boolean;
    CurContext: TFindContext;
  begin
    Result:=false;
    IdentContext:=CleanFindContext;
    IsPublished:=false;
    if (ClassContext.Node=nil)
    or (not (ClassContext.Node.Desc in AllClasses)) then begin
      DebugLn('TStandardCodeTool.CheckLFM.FindLFMIdentifier Internal error');
      exit;
    end;
    Params:=TFindDeclarationParams.Create;
    try
      Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
                     fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
                     fdfIgnoreOverloadedProcs];
      Params.ContextNode:=ClassContext.Node;
      Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
      try
        {DebugLn('FindLFMIdentifier A ',
          ' Ident=',
          '"'+GetIdentifier(Params.Identifier)+'"',
          ' Context="'+ClassContext.Node.DescAsString,'" "',StringToPascalConst(copy(ClassContext.Tool.Src,ClassContext.Node.StartPos,20))+'"',
          ' File="'+ExtractFilename(ClassContext.Tool.MainFilename)+'"',
          ' Flags=['+FindDeclarationFlagsAsString(Params.Flags)+']'
          );}
        if ClassContext.Tool.FindIdentifierInContext(Params) then begin
          IdentContext:=CleanFindContext;
          repeat
            CurContext:=CreateFindContext(Params);
            if (not IsPublished)
            and (CurContext.Node.HasParentOfType(ctnClassPublished)) then
              IsPublished:=true;

            if (IdentContext.Node=nil) then begin
              if (LFMNode.TheType<>lfmnProperty)
              or ((CurContext.Node.Desc=ctnProperty)
                  and (not CurContext.Tool.PropNodeIsTypeLess(CurContext.Node)))
              then
                IdentContext:=CurContext;
            end;

            if (IdentContext.Node<>nil) and IsPublished then break;

            // search further
            Params.Clear;
            Params.Flags:=[fdfSearchInAncestors,
                           fdfIgnoreMissingParams,
                           fdfIgnoreCurContextNode,
                           fdfIgnoreOverloadedProcs];
            Params.ContextNode:=CurContext.Node.Parent;
            while (Params.ContextNode<>nil)
            and (not (Params.ContextNode.Desc in AllClasses)) do
              Params.ContextNode:=Params.ContextNode.Parent;
            if Params.ContextNode=nil then break;
            Params.SetIdentifier(ClassContext.Tool,PChar(Pointer(IdentName)),nil);
            if not CurContext.Tool.FindIdentifierInContext(Params) then
            begin
              DebugLn(['FindLFMIdentifier ERROR ancestor of '+LFMNode.GetPath+' not found: ',FindContextToString(IdentContext),' IdentName=',IdentName]);
              break;
            end;
          until Params.NewNode=nil;
        end;
      except
        // ignore search/parse errors
        on E: ECodeToolError do ;
      end;
    finally
      Params.Free;
    end;

    IsMissingInCode := False;
    if (IdentContext.Node<>nil) and IsPublished then begin
      Result:=true;
    end else begin
      // no proper node found -> search in DefineProperties
      if SearchInDefinePropertiesToo then begin
        if FindNonPublishedDefineProperty(LFMNode,DefaultErrorPosition,IdentName,ClassContext)
        then begin
          //debugln(['FindLFMIdentifier "',IdentName,'" is defined via DefineProperties']);
          Result:=true;
        end;
      end
      else
        IsMissingInCode := True;
    end;
    if (not Result) and ErrorOnNotFound then begin
      if (IdentContext.Node<>nil) and (not IsPublished) then begin
        LFMTree.AddError(lfmeIdentifierNotPublished,LFMNode,
                         'identifier '+IdentName+' is not published in class '
                         +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
                         DefaultErrorPosition);
      end
      else if IsMissingInCode then begin
        LFMTree.AddError(lfmeIdentifierMissingInCode,LFMNode,
                         'identifier '+IdentName+' not found in pascal code '
                         +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
                         DefaultErrorPosition);
      end
      else begin
        LFMTree.AddError(lfmeIdentifierNotFound,LFMNode,
                         'identifier '+IdentName+' not found in class '
                         +'"'+ClassContext.Tool.ExtractClassName(ClassContext.Node,false,true)+'"',
                         DefaultErrorPosition);
      end;
    end;
  end;

  function FindClassNodeForLFMObject(LFMObject: TLFMObjectNode;
    const VarPropContext: TFindContext): TFindContext;
  var
    Params: TFindDeclarationParams;
    TypeNode: TCodeTreeNode;
    VariableTypeName, AnUnitName, TypeName: String;
  begin
    Result:=CleanFindContext;

    // check if identifier is a variable or property
    VariableTypeName:='';
    if (VarPropContext.Node.Desc=ctnVarDefinition) then begin
      TypeNode:=VarPropContext.Tool.FindTypeNodeOfDefinition(VarPropContext.Node);
      if TypeNode=nil then begin
        LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
                         LFMObject.Name+' has no type'
                         +CreateFootNote(VarPropContext),
                         LFMObject.NamePosition);
      end;
      VariableTypeName:=VarPropContext.Tool.ExtractDefinitionNodeType(VarPropContext.Node);
    end else if (VarPropContext.Node.Desc=ctnProperty) then begin
      TypeNode:=VarPropContext.Node;
      VariableTypeName:=VarPropContext.Tool.ExtractPropType(TypeNode,false,false);
    end else begin
      LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
                       LFMObject.Name+' is not a variable'
                       +CreateFootNote(VarPropContext),
                       LFMObject.NamePosition);
      exit;
    end;

    Params:=TFindDeclarationParams.Create;
    try
      Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
        fdfSearchInParentNodes,
        fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
        fdfIgnoreOverloadedProcs,fdfIgnoreCurContextNode];
      Params.ContextNode:=TypeNode;
      try
        Result:=VarPropContext.Tool.FindBaseTypeOfNode(Params,TypeNode);
      except
        // ignore search/parse errors
        on E: ECodeToolError do begin
          {$IFDEF VerboseCheckLFM}
          debugln(['FindClassNodeForLFMObject ',E.Message]);
          {$ENDIF}
        end;
      end;
    finally
      Params.Free;
    end;
    if (Result.Node=nil) then begin
      {$IFDEF VerboseCheckLFM}
      debugln(['FindClassNodeForLFMObject FindBaseTypeOfNode failed. LFMNode=',LFMObject.GetPath,' ',FindContextToString(CreateFindContext(VarPropContext.Tool,TypeNode))]);
      {$ENDIF}
      LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
                       'class '+VariableTypeName+' not found'
                       +CreateFootNote(VarPropContext),
                       LFMObject.TypeNamePosition);
      Result:=CleanFindContext;
      exit;
    end else if (not (Result.Node.Desc in AllClasses)) then begin
      {$IFDEF VerboseCheckLFM}
      debugln(['FindClassNodeForLFMObject base type is not a class. LFMNode=',LFMObject.GetPath,' ',FindContextToString(Result)]);
      {$ENDIF}
      LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
                       'class expected, but '+VariableTypeName+' found'
                       +CreateFootNote(VarPropContext),
                       LFMObject.TypeNamePosition);
      Result:=CleanFindContext;
      exit;
    end;

    // check classname
    TypeName:=Result.Tool.ExtractClassName(Result.Node,false);
    if not SameText(TypeName,LFMObject.TypeName) then begin
      LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
                     TypeName+' expected, but '+LFMObject.TypeName+' found'
                     +CreateFootNote(VarPropContext),
                     LFMObject.TypeNamePosition);
      exit;
    end;
    if LFMObject.TypeUnitName<>'' then begin
      // lfm has explicit unitname
      AnUnitName:=Result.Tool.GetSourceName(false);
      if not SameText(AnUnitName,LFMObject.TypeUnitName) then begin
        LFMTree.AddError(lfmeObjectIncompatible,LFMObject,
                       AnUnitName+' expected, but '+LFMObject.TypeUnitName+' found'
                       +CreateFootNote(VarPropContext),
                       LFMObject.TypeUnitNamePosition);
        exit;
      end;
    end;
  end;

  function FindClassContext(LFMObject: TLFMObjectNode): TFindContext;
  var
    Params: TFindDeclarationParams;
    Identifier: PChar;
    OldInput: TFindDeclarationInput;
    StartTool: TStandardCodeTool;
    aClassName: String;
  begin
    Result:=CleanFindContext;
    aClassName:=LFMObject.TypeName;

    if LFMObject.TypeUnitName<>'' then begin
      // type with unitname
      // -> search unit
      try
        Result.Tool:=FindCodeToolForUsedUnit(LFMObject.TypeUnitName,'',true);
      except
        // ignore search/parse errors
        on E: ECodeToolError do ;
      end;
      if Result.Tool=nil then begin
        LFMTree.AddError(lfmeUnitNotFound,LFMObject,
            'unit '+LFMObject.TypeUnitName+' not found',LFMObject.TypeUnitNamePosition);
        exit;
      end;

      // -> search class in unit interface
      try
        Result.Node:=Result.Tool.FindClassNodeInUnit(aClassName,true,false,true,false);
      except
        // ignore search/parse errors
        on E: ECodeToolError do ;
      end;
      if (Result.Node=nil)
          or (not (Result.Node.Desc in AllClasses)) then begin
        Result.Tool:=nil;
        LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
            'type '+aClassName+' not found',LFMObject.TypeNamePosition);
        exit;
      end;

      exit;
    end;

    // type without explicit unitname -> find declaration
    Params:=TFindDeclarationParams.Create;
    StartTool:=Self;
    Identifier:=PChar(Pointer(aClassName));
    try
      Params.Flags:=[fdfExceptionOnNotFound,
        fdfSearchInParentNodes,
        fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
        fdfIgnoreOverloadedProcs];
      Params.ContextNode:=FindInterfaceNode;
      if Params.ContextNode=nil then
        Params.ContextNode:=FindMainUsesNode;
      Params.SetIdentifier(StartTool,Identifier,nil);
      try
        Params.Save(OldInput);
        if FindIdentifierInContext(Params) then begin
          Params.Load(OldInput,true);
          Result:=Params.NewCodeTool.FindBaseTypeOfNode(Params,Params.NewNode);
          if (Result.Node=nil)
              or (not (Result.Node.Desc in AllClasses)) then
            Result:=CleanFindContext;
        end;
      except
        // ignore search/parse errors
        on E: ECodeToolError do ;
      end;
      if Result.Node=nil then begin
        LFMTree.AddError(lfmeIdentifierNotFound,LFMObject,
            'type '+aClassName+' not found',LFMObject.TypeNamePosition);
      end;
    finally
      Params.Free;
    end;
  end;

  procedure CheckLFMChildObject(LFMObject: TLFMObjectNode;
    const ParentContext: TFindContext; ContextIsDefault: boolean);
  var
    LFMObjectName: String;
    ChildContext: TFindContext;
    ClassContext: TFindContext;
    IdentifierFound: Boolean;
  begin
    // find variable for object

    // find identifier in Lookup Root
    LFMObjectName:=LFMObject.Name;
    //DebugLn('CheckChildObject A LFMObjectName="',LFMObjectName,'"');
    if LFMObjectName='' then begin
      LFMTree.AddError(lfmeObjectNameMissing,LFMObject,'missing object name',
                       LFMObject.StartPos);
      exit;
    end;

    ChildContext:=CleanFindContext;
    IdentifierFound:=(not ContextIsDefault) and
      FindLFMIdentifier(LFMObject,LFMObject.NamePosition,LFMObjectName,RootContext,
          false,ObjectsMustExist,ChildContext);

    //debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" IdentifierFound=',IdentifierFound,' ObjectsMustExist=',ObjectsMustExist,' ',FindContextToString(ChildContext)]);
    if IdentifierFound and (ObjectsMustExist or (ChildContext.Node<>nil)) then
    begin
      if ChildContext.Node=nil then begin
        // this is an extra entry, created via DefineProperties.
        // this depends on the runtime class, so codetools cannot check it
        exit;
      end;

      // find class node
      //debugln(['CheckLFMChildObject searching class node: LFMObjectName="',LFMObjectName,'" ',FindContextToString(CreateFindContext(ChildContext.Tool,DefinitionNode))]);
      ClassContext:=FindClassNodeForLFMObject(LFMObject,ChildContext);
      //debugln(['CheckLFMChildObject LFMObjectName="',LFMObjectName,'" class context: ',FindContextToString(ClassContext)]);
    end else begin
      // try the object type
      ClassContext:=FindClassContext(LFMObject);
      if ClassContext.Node=nil then exit;
    end;
    // check child LFM nodes
    if ClassContext.Node<>nil then
      CheckLFMObjectValues(LFMObject,ClassContext,false)
    else
      CheckLFMObjectValues(LFMObject,ParentContext,true);
  end;

  function FindClassNodeForPropertyType(LFMProperty: TLFMPropertyNode;
    DefaultErrorPosition: integer; const PropertyContext: TFindContext): TFindContext;
  var
    Params: TFindDeclarationParams;
  begin
    Result:=CleanFindContext;
    Params:=TFindDeclarationParams.Create;
    try
      Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,
        fdfSearchInParentNodes,
        fdfExceptionOnPredefinedIdent,fdfIgnoreMissingParams,
        fdfIgnoreOverloadedProcs];
      Params.ContextNode:=PropertyContext.Node;
      Params.SetIdentifier(PropertyContext.Tool,nil,nil);
      try
        Result:=PropertyContext.Tool.FindBaseTypeOfNode(Params,
                                                        PropertyContext.Node);
      except
        // ignore search/parse errors
        on E: ECodeToolError do ;
      end;
    finally
      Params.Free;
    end;
    if Result.Node=nil then begin
      LFMTree.AddError(lfmePropertyHasNoSubProperties,LFMProperty,
                       'property has no sub properties',
                       DefaultErrorPosition);
      exit;
    end;
  end;

  procedure CheckLFMProperty(LFMProperty: TLFMPropertyNode;
    const ParentContext: TFindContext);
  // checks properties. For example lines like 'OnShow = FormShow'
  // or 'VertScrollBar.Range = 29'
  // LFMProperty is the property node
  // ParentContext is the context, where properties are searched.
  //               This can be a class or a property.
  var
    i: Integer;
    CurName: string;
    CurPropertyContext: TFindContext;
    SearchContext: TFindContext;
  begin
    // find complete property name
    //DebugLn('CheckLFMProperty A LFMProperty Name="',LFMProperty.CompleteName,'" ParentContext=',FindContextToString(ParentContext));

    if LFMProperty.CompleteName='' then begin
      LFMTree.AddError(lfmePropertyNameMissing,LFMProperty,
                       'property without name',LFMProperty.StartPos);
      exit;
    end;

    // find every part of the property name
    SearchContext:=ParentContext;
    for i:=0 to LFMProperty.NameParts.Count-1 do begin
      if SearchContext.Node.Desc=ctnProperty then begin
        // get the type of the property and search the class node
        SearchContext:=FindClassNodeForPropertyType(LFMProperty,
          LFMProperty.NameParts.NamePositions[i],SearchContext);
        if SearchContext.Node=nil then exit;
      end;

      CurName:=LFMProperty.NameParts.Names[i];
      if not FindLFMIdentifier(LFMProperty,
                               LFMProperty.NameParts.NamePositions[i],
                               CurName,SearchContext,true,true,
                               CurPropertyContext)
      then
        break;
      if CurPropertyContext.Node=nil then begin
        // this is an extra entry, created via DefineProperties.
        // There is no generic way to test such things
        break;
      end;
      SearchContext:=CurPropertyContext;
    end;
    // ToDo: check value
  end;

  function CheckLFMObjectValues(LFMObject: TLFMObjectNode;
    const ClassContext: TFindContext; ContextIsDefault: boolean): boolean;
  var
    CurLFMNode: TLFMTreeNode;
  begin
    //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues A ',LFMObject.Name,':',LFMObject.TypeName);
    CurLFMNode:=LFMObject.FirstChild;
    while CurLFMNode<>nil do begin
      //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMObjectValues B ',CurLFMNode.ClassName);
      case CurLFMNode.TheType of
        lfmnObject:
          CheckLFMChildObject(TLFMObjectNode(CurLFMNode),ClassContext,ContextIsDefault);
        lfmnProperty:
          if not ContextIsDefault then
            CheckLFMProperty(TLFMPropertyNode(CurLFMNode),ClassContext);
      end;
      CurLFMNode:=CurLFMNode.NextSibling;
    end;
    Result:=true;
  end;

  function CheckLFMRoot(RootLFMNode: TLFMTreeNode): boolean;
  var
    LookupRootLFMNode: TLFMObjectNode;
    LookupRootTypeName, LookupRootTypeUnitName, CurUnitName: String;
    RootClassNode: TCodeTreeNode;
  begin
    Result:=false;

    //DebugLn('TStandardCodeTool.CheckLFM.CheckLFMRoot checking root ...');
    // get root object node
    if (RootLFMNode=nil) or (not (RootLFMNode is TLFMObjectNode)) then begin
      LFMTree.AddError(lfmeMissingRoot,nil,'missing root object',1);
      exit;
    end;
    LookupRootLFMNode:=TLFMObjectNode(RootLFMNode);

    // get type name of root object
    LookupRootTypeName:=LookupRootLFMNode.TypeName;
    if LookupRootTypeName='' then begin
      LFMTree.AddError(lfmeMissingRoot,nil,'missing type of root object',1);
      exit;
    end;

    // find root type
    LookupRootTypeUnitName:=LookupRootLFMNode.TypeUnitName;
    if LookupRootTypeUnitName<>'' then begin
      CurUnitName:=GetSourceName(false);
      if SameText(CurUnitName,LookupRootTypeUnitName) then begin
        // unitname fits
      end else if RootMustBeClassInIntf or RootMustBeClassInUnit then begin
        LFMTree.AddError(lfmeMissingRoot,LookupRootLFMNode,
                         'unitname '+LookupRootLFMNode.TypeUnitName+' mismatch',
                         LookupRootLFMNode.TypeUnitNamePosition);
        exit;
      end;
    end;

    if RootMustBeClassInIntf then begin
      RootClassNode:=FindClassNodeInInterface(LookupRootTypeName,true,false,false);
      RootContext:=CleanFindContext;
      RootContext.Node:=RootClassNode;
      RootContext.Tool:=Self;
    end else if RootMustBeClassInUnit then begin
      RootClassNode:=FindClassNodeInUnit(LookupRootTypeName,true,false,false,false);
      RootContext:=CleanFindContext;
      RootContext.Node:=RootClassNode;
      RootContext.Tool:=Self;
    end else begin
      RootContext:=FindClassContext(LookupRootLFMNode);
      RootClassNode:=RootContext.Node;
      if RootClassNode=nil then exit;
    end;
    if RootClassNode=nil then begin
      LFMTree.AddError(lfmeMissingRoot,LookupRootLFMNode,
                       'type '+LookupRootLFMNode.TypeName+' not found',
                       LookupRootLFMNode.TypeNamePosition);
      exit;
    end;
    Result:=CheckLFMObjectValues(LookupRootLFMNode,RootContext,false);
  end;

var
  CurRootLFMNode: TLFMTreeNode;
begin
  Result:=false;
  //DebugLn('TStandardCodeTool.CheckLFM A');
  // create tree from LFM file
  LFMTree:=DefaultLFMTrees.GetLFMTree(LFMBuf,true);
  ActivateGlobalWriteLock;
  try
    //DebugLn('TStandardCodeTool.CheckLFM parsing LFM ...');
    if not LFMTree.ParseIfNeeded then exit;
    // parse unit and find LookupRoot
    //DebugLn('TStandardCodeTool.CheckLFM parsing unit ...');
    BuildTree(lsrImplementationUsesSectionEnd);
    // find every identifier
    //DebugLn('TStandardCodeTool.CheckLFM checking identifiers ...');
    CurRootLFMNode:=LFMTree.Root;
    while CurRootLFMNode<>nil do begin
      if not CheckLFMRoot(CurRootLFMNode) then exit;
      CurRootLFMNode:=CurRootLFMNode.NextSibling;
    end;
  finally
    DeactivateGlobalWriteLock;
  end;
  Result:=LFMTree.FirstError=nil;
end;

function TStandardCodeTool.FindCreateFormStatement(StartPos: integer;
  const AClassName, AVarName: string;
  out Position: TAtomPosition): integer;
// 0=found, -1=not found, 1=found, but wrong classname
var MainBeginNode: TCodeTreeNode;
  ClassNameFits: boolean;
begin
  Result:=-1;
  Position.StartPos:=-1;
  if (AClassName='') or (AVarName='') or (length(AClassName)>255)
  or (length(AVarName)>255) then exit;
  if StartPos<1 then begin
    BuildTree(lsrEnd);
    MainBeginNode:=FindMainBeginEndNode;
    if MainBeginNode=nil then exit;
    StartPos:=MainBeginNode.StartPos;
    if StartPos<1 then exit;
  end;
  MoveCursorToCleanPos(StartPos);
  repeat
    ReadNextAtom;
    if UpAtomIs('APPLICATION') then begin
      Position.StartPos:=CurPos.StartPos;
      if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('CREATEFORM')
      and ReadNextAtomIsChar('(') then begin
        ReadNextAtom;
        ClassNameFits:=AtomIsIdentifier(PChar(AClassName));
        if ReadNextAtomIsChar(',')
        and (ReadNextAtomIsIdentifier(PChar(AVarName)) or (AVarName='*')) then begin
          if ReadNextAtomIsChar(')') then ReadNextAtomIsChar(';');
          Position.EndPos:=CurPos.EndPos;
          if ClassNameFits then
            Result:=0
          else
            Result:=1;
          exit;
        end;
      end;
    end;
  until (CurPos.StartPos>SrcLen);
  Result:=-1;
end;

function TStandardCodeTool.AddCreateFormStatement(const AClassName,
  AVarName: string; SourceChangeCache: TSourceChangeCache): boolean;
var MainBeginNode: TCodeTreeNode;
  OldPosition: TAtomPosition;
  FromPos, ToPos, Indent: integer;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  if (AClassName='') or (length(AClassName)>255) or (AVarName='')
  or (length(AVarName)>255) then exit;
  BuildTree(lsrEnd);
  MainBeginNode:=FindMainBeginEndNode;
  if MainBeginNode=nil then exit;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  FromPos:=-1;
  if FindCreateFormStatement(MainBeginNode.StartPos,AClassName,
    AVarName,OldPosition)=-1
  then begin
    // does not exist -> create as last in front of 'Application.Run'
    MoveCursorToCleanPos(MainBeginNode.StartPos);
    repeat
      if ReadNextUpAtomIs('APPLICATION') then begin
        FromPos:=CurPos.StartPos;
        if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
          break;
        end;
        FromPos:=-1;
      end;
    until (CurPos.StartPos>SrcLen);
    if FromPos<1 then exit;
    SourceChangeCache.MainScanner:=Scanner;
    Indent:=Beauty.GetLineIndent(Src,FromPos);
    FromPos:=FindLineEndOrCodeInFrontOfPosition(FromPos);
    SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,FromPos,
       Beauty.BeautifyStatement(
         'Application.CreateForm('+AClassName+','+AVarName+');',Indent));
  end else begin
    // it exists -> replace it
    FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
    ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
    SourceChangeCache.MainScanner:=Scanner;
    SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
       Beauty.BeautifyStatement(
         'Application.CreateForm('+AClassName+','+AVarName+');',
         Beauty.Indent));
  end;
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.RemoveCreateFormStatement(const AVarName: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var Position: TAtomPosition;
  FromPos, ToPos: integer;
begin
  Result:=false;
  if FindCreateFormStatement(-1,'*',AVarName,Position)=-1 then
    exit;
  FromPos:=FindLineEndOrCodeInFrontOfPosition(Position.StartPos);
  ToPos:=FindLineEndOrCodeAfterPosition(Position.EndPos);
  SourceChangeCache.MainScanner:=Scanner;
  SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'');
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.ChangeCreateFormStatement(StartPos: integer;
  const OldClassName, OldVarName: string;
  const NewClassName, NewVarName: string;
  OnlyIfExists: boolean; SourceChangeCache: TSourceChangeCache): boolean;
var MainBeginNode: TCodeTreeNode;
  OldPosition: TAtomPosition;
  FromPos, ToPos: integer;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  if (OldClassName='') or (length(OldClassName)>255)
  or (OldVarName='') or (length(OldVarName)>255)
  or (NewClassName='') or (length(NewClassName)>255)
  or (NewVarName='') or (length(NewVarName)>255)
  then exit;
  BuildTree(lsrEnd);
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  if StartPos<1 then begin
    MainBeginNode:=FindMainBeginEndNode;
    if MainBeginNode=nil then exit;
    StartPos:=MainBeginNode.StartPos;
    if StartPos<1 then exit;
  end;
  FromPos:=-1;
  if FindCreateFormStatement(StartPos,OldClassName,
    OldVarName,OldPosition)=-1 then begin
    // does not exist
    if OnlyIfExists then begin
      Result:=true;
      exit;
    end;
    Result:=AddCreateFormStatement(NewClassName,NewVarName,SourceChangeCache);
  end else begin
    // replace
    FromPos:=FindLineEndOrCodeInFrontOfPosition(OldPosition.StartPos);
    ToPos:=FindLineEndOrCodeAfterPosition(OldPosition.EndPos);
    SourceChangeCache.MainScanner:=Scanner;
    SourceChangeCache.Replace(gtNewLine,gtNewLine,FromPos,ToPos,
       Beauty.BeautifyStatement(
         'Application.CreateForm('+NewClassName+','+NewVarName+');',
         Beauty.Indent));
    Result:=SourceChangeCache.Apply;
  end;
end;

function TStandardCodeTool.ListAllCreateFormStatements: TStrings;
// list format: VarName:ClassName
var Position: integer;
  StatementPos: TAtomPosition;
  s:string;
  var MainBeginNode: TCodeTreeNode;
begin
  BuildTree(lsrEnd);
  Result:=TStringList.Create;
  MainBeginNode:=FindMainBeginEndNode;
  if MainBeginNode=nil then exit;
  Position:=MainBeginNode.StartPos;
  repeat
    if FindCreateFormStatement(Position,'*','*',StatementPos)=-1 then
      exit;
    Position:=StatementPos.EndPos;
    MoveCursorToCleanPos(StatementPos.StartPos);
    ReadNextAtom; // read 'Application'
    ReadNextAtom; // read '.'
    ReadNextAtom; // read 'CreateForm'
    ReadNextAtom; // read '('
    ReadNextAtom; // read class name
    s:=GetAtom;
    ReadNextAtom; // read ','
    ReadNextAtom; // read variable name
    s:=GetAtom+':'+s;
    Result.Add(s);
  until false;
end;

function TStandardCodeTool.SetAllCreateFromStatements(List: TStrings;
  SourceChangeCache: TSourceChangeCache): boolean;
{ every string in the list has the format VarName:ClassName
  or simply VarName In the latter case it will be automatically expanded
  to VarName:TVarName
}
var Position, InsertPos, i, ColonPos, Indent: integer;
  StatementPos: TAtomPosition;
  MainBeginNode: TCodeTreeNode;
  AClassName, AVarName: string;
  LastEndPos: Integer;
  Beauty: TBeautifyCodeOptions;
begin
  Result:= false;
  if (List = nil) or (SourceChangeCache = nil) then exit;
  BuildTree(lsrEnd);

  { first delete all CreateForm Statements }
  SourceChangeCache.MainScanner:= Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  MainBeginNode:=FindMainBeginEndNode;
  if MainBeginNode = nil then exit;
  Position:=MainBeginNode.StartPos;
  InsertPos:=-1;
  LastEndPos:=-1;
  repeat
    if FindCreateFormStatement(Position, '*', '*', StatementPos) = -1 then break;

    Position:=StatementPos.EndPos;
    StatementPos.StartPos:=FindLineEndOrCodeInFrontOfPosition(StatementPos.StartPos);
    if (LastEndPos>0) and (StatementPos.StartPos<LastEndPos) then
      StatementPos.StartPos:=LastEndPos;
    if InsertPos < 1 then InsertPos:= StatementPos.StartPos;

    StatementPos.EndPos:=FindLineEndOrCodeAfterPosition(StatementPos.EndPos);
    LastEndPos:=StatementPos.EndPos;

    if not SourceChangeCache.Replace(gtNone,gtNone, StatementPos.StartPos, StatementPos.EndPos, '') then
      exit;
  until false;

  Result:=SourceChangeCache.Apply;
  if not Result then exit;

  { then add all CreateForm Statements }
  if InsertPos < 1 then begin

    { there was no createform statement -> insert in front of Application.Run }
    MoveCursorToCleanPos(MainBeginNode.StartPos);
    repeat
      if ReadNextUpAtomIs('APPLICATION') then begin
        InsertPos:=CurPos.StartPos;
        if ReadNextAtomIsChar('.') and ReadNextUpAtomIs('RUN') then begin
          InsertPos:=FindLineEndOrCodeInFrontOfPosition(InsertPos);
          break;
        end;
        InsertPos:=-1;
      end;
    until (CurPos.StartPos>SrcLen);
    if InsertPos < 1 then exit;
  end;

  for i:= 0 to List.Count - 1 do begin
    if Length(List[i]) <= 1 then continue;

    ColonPos:= Pos(List[i], ':');
    if (ColonPos > 1) then begin
      AVarName:= Copy(List[i], 1, ColonPos);
      AClassName:= Copy(List[i], ColonPos + 1, Length(List[i]) - ColonPos);
    end else begin
      AVarName:= List[i];  
      AClassName:= 'T' + AVarName;
    end;  
    Indent:=Beauty.GetLineIndent(Src, InsertPos);

    SourceChangeCache.Replace(gtNewLine, gtNewLine, InsertPos, InsertPos,
      SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
        'Application.CreateForm('+AClassName+','+AVarName+');', Indent));
  end;
  Result:= Result and SourceChangeCache.Apply;
end;

function TStandardCodeTool.SetApplicationScaledStatement(
  const NewScaled: boolean; SourceChangeCache: TSourceChangeCache): boolean;
begin
  Result := SetApplicationStatement('Scaled', BoolToStr(NewScaled, True), SourceChangeCache);
end;

function TStandardCodeTool.SetApplicationStatement(const APropertyName,
  NewCode: string; SourceChangeCache: TSourceChangeCache): boolean;
var
  StartPos, ConstStartPos, EndPos: integer;
  NewStatement: String;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  // search old Application.APropertyName:=XYZ statement
  FindApplicationStatement(UpperCase(APropertyName),StartPos,ConstStartPos,EndPos);
  // create statement. FindApplicationStatement always returns an insert point.
  NewStatement:='Application.'+APropertyName+':='+NewCode+';';
  NewStatement:=Beauty.BeautifyStatement(NewStatement,Beauty.Indent);
  SourceChangeCache.MainScanner:=Scanner;
  if not SourceChangeCache.Replace(gtNewLine,gtNewLine,StartPos,EndPos,NewStatement) then
    exit;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.FindApplicationTitleStatement(out StartPos,
  StringConstStartPos, EndPos: integer): boolean;
begin
  Result := FindApplicationStatement('TITLE', StartPos, StringConstStartPos, EndPos);
end;

function TStandardCodeTool.GetApplicationTitleStatement(StringConstStartPos,
  EndPos: integer; var Title: string): boolean;
var
  FormatStringParams: string;
begin
  Result:=false;
  Title:='';
  if (StringConstStartPos<1) or (StringConstStartPos>SrcLen) then exit;
  MoveCursorToCleanPos(StringConstStartPos);
  ReadNextAtom;
  if not AtomIsStringConstant then exit;
  Result:=GetStringConstAsFormatString(StringConstStartPos,EndPos,Title,
                                       FormatStringParams);
  if FormatStringParams='' then ;
end;

function TStandardCodeTool.SetApplicationTitleStatement(const NewTitle: string;
  SourceChangeCache: TSourceChangeCache): boolean;
begin
  Result := SetApplicationStatement('Title', StringToPascalConst(NewTitle), SourceChangeCache);
end;

function TStandardCodeTool.RemoveApplicationTitleStatement(
  SourceChangeCache: TSourceChangeCache): boolean;
begin
  Result := RemoveApplicationStatement('TITLE', SourceChangeCache);
end;

function TStandardCodeTool.RenameForm(const OldFormName,
  OldFormClassName: string; const NewFormName, NewFormClassName: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  IdentList: TStringList;
begin
  Result:=false;
  if (OldFormName='') or (OldFormClassName='')
  or (NewFormName='') or (NewFormClassName='')
  or (SourceChangeCache=nil) then exit;
  if (OldFormName=NewFormName)
  and (OldFormClassName=NewFormClassName) then exit;
  IdentList:=TStringList.Create;
  try
    if (OldFormName<>NewFormName) then begin
      IdentList.Add(OldFormName);
      IdentList.Add(NewFormName);
    end;
    if (OldFormClassName<>NewFormClassName) then begin
      IdentList.Add(OldFormClassName);
      IdentList.Add(NewFormClassName);
    end;
    Result:=ReplaceWords(IdentList,false,SourceChangeCache);
  finally
    IdentList.Free;
  end;
end;

function TStandardCodeTool.FindFormAncestor(const AClassName: string;
  out AncestorClassName: string): boolean;
var
  ClassNode: TCodeTreeNode;
begin
  Result:=false;
  AncestorClassName:='';
  if AClassName='' then exit;
  BuildTree(lsrImplementationStart);
  ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
  if (ClassNode=nil) then exit;
  // search the ancestor name
  MoveCursorToNodeStart(ClassNode);
  ReadNextAtom; // read keyword 'class', 'object', 'interface', 'dispinterface'
  while UpAtomIs('SEALED') or UpAtomIs('ABSTRACT') do ReadNextAtom;    
  if UpAtomIs('PACKED') or UpAtomIs('BITPACKED') then ReadNextAtom;
  ReadNextAtom;
  if AtomIsChar('(') then begin
    ReadNextAtom;
    if UpAtomIs('SPECIALIZE') then
      ReadNextAtom;
    if AtomIsIdentifier then
      AncestorClassName:=GetAtom;
  end;
  if AncestorClassName='' then
    AncestorClassName:='TObject';
  Result:=true;
end;

{-------------------------------------------------------------------------------
  function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
    ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache): boolean;
    
  Search in all used sources (not only the cleaned source) for identifiers.
  It will find all identifiers, except identifiers in compiler directives.
  This includes identifiers in string constants and comments.
  
  ChangeStrings = true, means to replace in string constants too
-------------------------------------------------------------------------------}
function TStandardCodeTool.ReplaceWords(IdentList: TStrings;
  ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
  SkipPointWords: boolean): boolean;

  function CheckIdentifier(const CurSource: string;
    IdentStart: integer): boolean;
  var
    p: integer;
  begin
    if not SkipPointWords then exit(true);
    p:=IdentStart-1;
    while (p>0) and (IsSpaceChar[CurSource[p]]) do dec(p);
    Result:=(p<1) or (CurSource[p]<>'.');
  end;

  procedure ReplaceWordsInSource(ACode: TCodeBuffer);
  var
    StartPos, EndPos, MaxPos, IdentStart, IdentEnd: integer;
    CurSource: string;
    i: integer;
  begin
    CurSource:=ACode.Source;
    MaxPos:=length(CurSource);
    StartPos:=1;
    // go through all source parts between compiler directives
    //DebugLn('TStandardCodeTool.ReplaceWords ',ACode.Filename);
    repeat
      EndPos:=FindNextCompilerDirective(CurSource,StartPos,
                                        Scanner.NestedComments);
      if EndPos>MaxPos then EndPos:=MaxPos+1;
      // search all identifiers
      repeat
        if ChangeStrings then
          IdentStart:=FindNextIdentifier(CurSource,StartPos,EndPos-1)
        else
          IdentStart:=FindNextIdentifierSkipStrings(CurSource,StartPos,EndPos-1);
        if IdentStart>=EndPos then
          break;
        i:=0;
        while i<IdentList.Count do begin
          if (IdentList[i]<>'')
          and (BasicCodeTools.CompareIdentifiers(PChar(Pointer(IdentList[i])),
                                                 @CurSource[IdentStart])=0)
          and CheckIdentifier(CurSource,IdentStart)
          and (IdentList[i]<>IdentList[i+1])
          then begin
            // identifier found -> replace
            IdentEnd:=IdentStart+length(IdentList[i]);
            //DebugLn('TStandardCodeTool.ReplaceWords replacing: ',
            //' "',copy(CurSource,IdentStart,IdentEnd-IdentStart),'" -> "',IdentList[i+1],'" at ',IdentStart
            //);
            SourceChangeCache.ReplaceEx(gtNone,gtNone,1,1,
              ACode,IdentStart,IdentEnd,IdentList[i+1]);
            break;
          end;
          inc(i,2);
        end;
        // skip identifier
        StartPos:=IdentStart;
        while (StartPos<MaxPos) and IsIdentChar[CurSource[StartPos]] do
          inc(StartPos);
      until false;
      if EndPos<=MaxPos then begin
        // skip comment
        StartPos:=FindCommentEnd(CurSource,EndPos,Scanner.NestedComments);
        if StartPos>MaxPos then break;
      end else begin
        break;
      end;
    until false;
  end;
  
var
  SourceList: TFPList;
  i: integer;
begin
  Result:=false;
  if (IdentList=nil) or (IdentList.Count=0) or (SourceChangeCache=nil)
  or (Odd(IdentList.Count)) then exit;
  BuildTree(lsrEnd);
  if Scanner=nil then exit;
  SourceChangeCache.MainScanner:=Scanner;
  SourceList:=TFPList.Create;
  try
    Scanner.FindCodeInRange(1,SrcLen,SourceList);
    for i:=0 to SourceList.Count-1 do begin
      ReplaceWordsInSource(TCodeBuffer(SourceList[i]));
    end;
  finally
    SourceList.Free;
  end;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.FindNearestIdentifierNode(
  const CursorPos: TCodeXYPosition; IdentTree: TAVLTree): TAVLTreeNode;
var
  CleanCursorPos: integer;
  BestDiff: Integer;
  CurIdentNode: TAVLTreeNode;
  CurDiff: Integer;
begin
  Result:=nil;
  if IdentTree=nil then exit;
  BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,[]);
  BestDiff:=SrcLen+1;
  MoveCursorToCleanPos(1);
  repeat
    ReadNextAtom;
    if AtomIsIdentifier then begin
      CurIdentNode:=
        IdentTree.FindKey(@Src[CurPos.StartPos],
                          TListSortCompare(@CompareIdentifiers));
      if CurIdentNode<>nil then begin
        CurDiff:=CurPos.StartPos-CleanCursorPos;
        if CurDiff<0 then CurDiff:=-CurDiff;
        if (Result=nil) or (CurDiff<BestDiff) then begin
          BestDiff:=CurDiff;
          Result:=CurIdentNode;
        end;
      end;
    end;
  until CurPos.EndPos>SrcLen;
end;

function TStandardCodeTool.ReplaceWord(const OldWord, NewWord: string;
  ChangeStrings: boolean; SourceChangeCache: TSourceChangeCache;
  SkipPointWords: boolean): boolean;
var
  IdentList: TStringList;
begin
  Result:=false;
  if OldWord='' then exit;
  if OldWord=NewWord then exit(true);
  if (SourceChangeCache=nil) then exit;
  IdentList:=TStringList.Create;
  try
    IdentList.Add(OldWord);
    IdentList.Add(NewWord);
    Result:=ReplaceWords(IdentList,ChangeStrings,SourceChangeCache,SkipPointWords);
  finally
    IdentList.Free;
  end;
end;

function TStandardCodeTool.CommentCode(const StartPos, EndPos: integer;
  SourceChangeCache: TSourceChangeCache; Apply: boolean): boolean;
var
  i: LongInt;
  CurStartPos: LongInt;
  CommentNeeded: Boolean;
  CurEndPos: LongInt;
begin
  if StartPos>=EndPos then
    RaiseException(20170421201123,'TStandardCodeTool CommentCode');

  Result:=false;
  // comment with curly brackets {}
  i:=StartPos;
  CurStartPos:=i;
  CurEndPos:=CurStartPos;
  CommentNeeded:=false;
  repeat
    //debugln(['TPascalReaderTool.CommentCode ',dbgstr(Src[i]),' Needed=',CommentNeeded,' ',dbgstr(copy(Src,CurStartPos,CurEndPos-CurStartPos))]);
    if (Src[i]='{') or (i>=EndPos) then begin
      // the area contains a comment -> comment in front
      if CommentNeeded then begin
        if not SourceChangeCache.Replace(gtNone,gtNone,
          CurStartPos,CurStartPos,'{') then exit;
        if not SourceChangeCache.Replace(gtNone,gtNone,
          CurEndPos,CurEndPos,'}') then exit;
        //DebugLn('Comment "',copy(Src,CurStartPos,i-CurStartPos),'"');
        CommentNeeded:=false;
      end;
      if i>=EndPos then break;
      // skip comment
      i:=FindCommentEnd(Src,i,Scanner.NestedComments)-1;
    end else if not IsSpaceChar[Src[i]] then begin
      if not CommentNeeded then begin
        CurStartPos:=i;
        CommentNeeded:=true;
      end;
      CurEndPos:=i+1;
    end;
    inc(i);
  until false;
  if Apply then
    Result:=SourceChangeCache.Apply
  else
    Result:=true;
end;

function TStandardCodeTool.GetStringConstBounds(
  const CursorPos: TCodeXYPosition;
  out StartPos, EndPos: TCodeXYPosition; ResolveComments: boolean): boolean;
// examples:
//   's1'+'s2'#13+AFunction(...)+inherited AMethod
{ $DEFINE VerboseGetStringConstBounds}
type
  TStrConstTokenType = (scatNone, scatStrConst, scatPlus, scatIdent,
    scatInherited, scatPoint, scatUp,
    scatEdgedBracketOpen, scatEdgedBracketClose,
    scatRoundBracketOpen, scatRoundBracketClose);

  {$IFDEF VerboseGetStringConstBounds}
  function EnumToStr(TokenType: TStrConstTokenType): string;
  begin
    WriteStr(Result, TokenType);
  end;
  {$ENDIF}

  function GetCurrentTokenType: TStrConstTokenType;
  begin
    if (CurPos.StartPos<1) or (CurPos.StartPos>SrcLen) then
      Result:=scatNone
    else if AtomIsStringConstant then
      Result:=scatStrConst
    else if AtomIsChar('+') then
      Result:=scatPlus
    else if AtomIsIdentifier then
      Result:=scatIdent
    else if UpAtomIs('INHERITED') then
      Result:=scatInherited
    else if CurPos.Flag=cafPoint then
      Result:=scatPoint
    else if AtomIsChar('^') then
      Result:=scatUp
    else if CurPos.Flag=cafRoundBracketOpen then
      Result:=scatRoundBracketOpen
    else if CurPos.Flag=cafRoundBracketClose then
      Result:=scatRoundBracketClose
    else if CurPos.Flag=cafEdgedBracketOpen then
      Result:=scatEdgedBracketOpen
    else if CurPos.Flag=cafEdgedBracketClose then
      Result:=scatEdgedBracketClose
    else
      Result:=scatNone;
  end;

var
  CleanCursorPos: integer;
  SameArea: TAtomPosition;
  LastToken, CurrentToken: TStrConstTokenType;
  StartCleanPos, EndCleanPos: integer;
  StringConstantFound: Boolean;
begin
  StartPos:=CursorPos;
  EndPos:=CursorPos;
  Result:=true;
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  {$IFDEF VerboseGetStringConstBounds}
  DebugLn('TStandardCodeTool.GetStringConstBounds A Start at ',CleanPosToStr(CleanCursorPos),' "',copy(Src,CleanCursorPos-5,5),'" | "',copy(Src,CleanCursorPos,5),'"');
  {$ENDIF}
  GetCleanPosInfo(-1,CleanCursorPos,ResolveComments,SameArea);
  {$IFDEF VerboseGetStringConstBounds}
  DebugLn('TStandardCodeTool.GetStringConstBounds B Same Area: ',CleanPosToStr(SameArea.StartPos),'-',CleanPosToStr(SameArea.EndPos),' "',copy(Src,SameArea.StartPos,SameArea.EndPos-SameArea.StartPos),'"');
  {$ENDIF}
  if (SameArea.EndPos=SameArea.StartPos) or (SameArea.StartPos>SrcLen) then
    exit;

  // read til end of string constant
  MoveCursorToCleanPos(SameArea.StartPos);
  ReadNextAtom;
  {$IFDEF VerboseGetStringConstBounds}
  DebugLn('TStandardCodeTool.GetStringConstBounds read til end of string Atom=',GetAtom);
  {$ENDIF}
  CurrentToken:=GetCurrentTokenType;
  if (CurrentToken=scatNone) then exit;
  StringConstantFound:=(CurrentToken=scatStrConst);
  repeat
    EndCleanPos:=CurPos.EndPos;
    ReadNextAtom;
    LastToken:=CurrentToken;
    CurrentToken:=GetCurrentTokenType;
    {$IFDEF VerboseGetStringConstBounds}
    DebugLn('TStandardCodeTool.GetStringConstBounds Read Forward: ',GetAtom,' EndCleanPos=',dbgs(EndCleanPos),
      ' LastToken=',EnumToStr(LastToken),
      ' CurrentToken=',EnumToStr(CurrentToken),' ',EnumToStr(GetCurrentTokenType));
    {$ENDIF}
    case CurrentToken of
    scatNone, scatEdgedBracketClose, scatRoundBracketClose:
      if not (LastToken in [scatStrConst,scatIdent,scatUp,
         scatEdgedBracketClose, scatRoundBracketClose])
      then
        exit
      else
        break;

    scatStrConst:
      if not (LastToken in [scatPlus]) then
        exit
      else
        StringConstantFound:=true;
      
    scatPlus:
      if not (LastToken in [scatStrConst, scatIdent, scatUp,
        scatEdgedBracketClose, scatRoundBracketClose]) then exit;

    scatIdent:
      if not (LastToken in [scatPlus, scatPoint, scatInherited]) then exit;

    scatInherited:
      if not (LastToken in [scatPlus, scatPoint]) then exit;

    scatPoint:
      if not (LastToken in [scatIdent, scatUp, scatRoundBracketClose,
                            scatEdgedBracketClose]) then
        exit;

    scatEdgedBracketOpen,scatRoundBracketOpen:
      if not (LastToken in [scatIdent, scatUp]) then
        exit
      else begin
        ReadTilBracketClose(true);
        CurrentToken:=GetCurrentTokenType;
      end;
      
    end;
  until false;

  // read til start of string constant
  MoveCursorToCleanPos(SameArea.StartPos);
  ReadNextAtom;
  {$IFDEF VerboseGetStringConstBounds}
  DebugLn('TStandardCodeTool.GetStringConstBounds Read til start of string ',GetAtom);
  {$ENDIF}
  CurrentToken:=GetCurrentTokenType;
  repeat
    StartCleanPos:=CurPos.StartPos;
    ReadPriorAtom;
    {$IFDEF VerboseGetStringConstBounds}
    DebugLn('TStandardCodeTool.GetStringConstBounds Read backward: ',GetAtom,' StartCleanPos=',dbgs(StartCleanPos));
    {$ENDIF}
    LastToken:=CurrentToken;
    CurrentToken:=GetCurrentTokenType;
    case CurrentToken of
    scatNone, scatEdgedBracketOpen, scatRoundBracketOpen:
      if not (LastToken in [scatStrConst,scatIdent,scatPlus]) then
        exit
      else
        break;

    scatStrConst:
      if not (LastToken in [scatPlus]) then
        exit
      else
        StringConstantFound:=true;

    scatPlus:
      if not (LastToken in [scatStrConst, scatIdent, scatRoundBracketOpen]) then
        exit;

    scatIdent:
      if not (LastToken in [scatPlus, scatPoint, scatUp, scatRoundBracketOpen,
        scatEdgedBracketOpen]) then exit;

    scatInherited:
      if not (LastToken in [scatIdent]) then exit;

    scatPoint:
      if not (LastToken in [scatIdent]) then exit;

    scatEdgedBracketClose,scatRoundBracketClose:
      if not (LastToken in [scatPlus, scatUp, scatPoint]) then
        exit
      else begin
        ReadBackTilBracketOpen(true);
        CurrentToken:=GetCurrentTokenType;
      end;

    end;
  until false;
  
  // convert start and end position
  {$IFDEF VerboseGetStringConstBounds}
  DebugLn('TStandardCodeTool.GetStringConstBounds END "',copy(Src,StartCleanPos,EndCleanPos-StartCleanPos),'" StringConstantFound=',dbgs(StringConstantFound));
  {$ENDIF}
  if not StringConstantFound then begin
    EndCleanPos:=StartCleanPos;
  end;
  if not CleanPosToCaret(StartCleanPos,StartPos) then exit;
  if not CleanPosToCaret(EndCleanPos,EndPos) then exit;

  Result:=true;
end;

function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
  EndPos: integer; out FormatStringConstant, FormatParameters: string;
  out StartInStringConst, EndInStringConst: boolean): boolean;
{ Converts a string constant into the parameters for a Format call of the
  system unit.

  Examples:

  'Hallo'           -> "Hallo", ""
  'A'+IntToStr(1)   -> "A%s", "IntToStr(1)"
  'A%B'#13#10       -> "A%sB%s", "'%', #13#10"
}
  procedure AddChar(c: char);
  begin
    FormatStringConstant:=FormatStringConstant+c;
  end;

  procedure AddParameter(const NewParam: string);
  begin
    FormatStringConstant:=FormatStringConstant+'%s';
    if FormatParameters<>'' then
      FormatParameters:=FormatParameters+',';
    FormatParameters:=FormatParameters+NewParam;
  end;

  procedure AddParameter(ParamStartPos,ParamEndPos: integer);
  begin
    AddParameter(copy(Src,ParamStartPos,ParamEndPos-ParamStartPos));
  end;

  procedure ConvertStringConstant;
  var
    APos: Integer;
    CharConstStart: Integer;
    InRange: Boolean;
  begin
    if (CurPos.StartPos<StartPos) and (CurPos.EndPos>StartPos) then
      StartInStringConst:=true;
    if (CurPos.StartPos<EndPos) and (CurPos.EndPos>EndPos) then
      EndInStringConst:=true;

    APos:=CurPos.StartPos;
    while APos<EndPos do begin
      InRange:=(APos>=StartPos);
      //debugln('ConvertStringConstant InRange=',dbgs(InRange),' Src[APos]=',Src[APos]);
      if Src[APos]='''' then begin
        // read string constant
        inc(APos);
        while APos<EndPos do begin
          InRange:=(APos>=StartPos);
          case Src[APos] of
          '''':
            if (APos<EndPos-1) and (Src[APos+1]='''') then begin
              // a double ' means a single '
              if InRange then begin
                AddChar('''');
                AddChar('''');
              end;
              inc(APos,2);
            end else begin
              // a single ' means end of string constant
              inc(APos);
              break;
            end;
          else
            begin
              // normal char
              if InRange then
                AddChar(Src[APos]);
              inc(APos);
            end;
          end;
        end;
      end else if Src[APos]='#' then begin
        CharConstStart:=APos;
        InRange:=(APos+1>=StartPos);
        repeat
          // read char constant
          inc(APos);
          if APos<EndPos then begin
            if Src[APos-1]='#' then begin
              if IsNumberChar[Src[APos]] then begin
                // read decimal number
                while (APos<EndPos) and IsNumberChar[Src[APos]] do
                  inc(APos);
              end else if Src[APos]='$' then begin
                // read hexnumber
                while (APos<EndPos) and IsHexNumberChar[Src[APos]] do
                  inc(APos);
              end;
            end;
          end;
        until (APos>=EndPos) or (Src[APos]<>'#');
        if InRange then
          AddParameter(CharConstStart,APos);
      end else
        break;
    end;
  end;

  procedure ConvertOther;
  var
    ParamStartPos: Integer;
    ParamEndPos: Integer;
  begin
    // read till next string constant
    ParamStartPos:=CurPos.StartPos;
    ParamEndPos:=ParamStartPos;
    while (not AtomIsStringConstant) and (CurPos.EndPos<=EndPos) do begin
      if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
        ReadTilBracketClose(true);
      if not AtomIsChar('+') then ParamEndPos:=CurPos.EndPos;
      ReadNextAtom;
    end;
    if ParamEndPos>ParamStartPos then
      AddParameter(ParamStartPos,ParamEndPos);
    if AtomIsStringConstant then UndoReadNextAtom;
  end;

var
  ANode: TCodeTreeNode;
  CodePosInFront: LongInt;
begin
  Result:=false;
  // read string constants and convert it
  FormatStringConstant:='';
  FormatParameters:='';
  StartInStringConst:=false;
  EndInStringConst:=false;
  ANode:=FindDeepestNodeAtPos(StartPos,True);
  CodePosInFront:=ANode.StartPos;
  MoveCursorToCleanPos(CodePosInFront);
  if EndPos>SrcLen then EndPos:=SrcLen+1;
  repeat
    ReadNextAtom;
    //debugln('GetStringConstAsFormatString Atom=',GetAtom);
    if (CurPos.StartPos>=EndPos) then break;
    if CurPos.EndPos>StartPos then begin
      //debugln('GetStringConstAsFormatString Parsing...');
      if AtomIsStringConstant then begin
        // a string constant
        ConvertStringConstant;
      end else if AtomIsChar('+') then begin
        // simply ignore
      end else if (CurPos.Flag=cafRoundBracketOpen) or AtomIsIdentifier
      then begin
        // add as parameter
        ConvertOther;
      end else
        // string constant end
        break;
    end;
  until false;
  Result:=FormatStringConstant<>'';
end;

function TStandardCodeTool.GetStringConstAsFormatString(StartPos,
  EndPos: integer; out FormatStringConstant, FormatParameters: string
    ): boolean;
var
  StartInStringConst, EndInStringConstant: boolean;
begin
  Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
                       FormatParameters,StartInStringConst,EndInStringConstant);
  if StartInStringConst then ;
  if EndInStringConstant then ;
end;

function TStandardCodeTool.ExtractOperand(const CursorPos: TCodeXYPosition; out
  Operand: string; WithPostTokens, WithAsOperator,
  WithoutTrailingPoints: boolean): boolean;
var
  CleanPos: integer;
  StartPos: LongInt;
  EndPos: LongInt;
  Node: TCodeTreeNode;
begin
  Result:=false;
  Operand:='';
  if CursorPos.Code.LineColIsSpace(CursorPos.Y,CursorPos.X) then exit;
  BuildTreeAndGetCleanPos(CursorPos,CleanPos);
  Node:=FindDeepestNodeAtPos(CleanPos,true);
  StartPos:=FindStartOfTerm(CleanPos,NodeTermInType(Node));
  if StartPos<1 then exit;
  StartPos:=FindNextNonSpace(Src,StartPos);
  if StartPos>CleanPos then exit;
  EndPos:=FindEndOfTerm(CleanPos,false,WithAsOperator);
  if not WithPostTokens then begin
    MoveCursorToCleanPos(CleanPos);
    ReadNextAtom;
    if CurPos.EndPos<EndPos then
      EndPos:=CurPos.EndPos;
  end;
  if EndPos<1 then exit;
  //DebugLn(['TStandardCodeTool.ExtractOperand "',dbgstr(copy(Src,StartPos,EndPos-StartPos)),'"']);
  Operand:=ExtractCode(StartPos,EndPos,[phpCommentsToSpace]);
  if WithoutTrailingPoints then begin
    while (Operand<>'') and (Operand[length(Operand)]='.') do
      Operand:=copy(Operand,1,length(Operand)-1);
  end;
  Result:=true;
end;

function TStandardCodeTool.FindApplicationScaledStatement(out StartPos,
  BooleanConstStartPos, EndPos: integer): boolean;
begin
  Result := FindApplicationStatement('SCALED', StartPos, BooleanConstStartPos, EndPos);
end;

function TStandardCodeTool.FindApplicationStatement(const APropertyUpCase: string;
  out StartPos, ConstStartPos, EndPos: integer): boolean;
// Find statement "Application.APropertyUpCase:=XYZ;" and return True if found.
// Also return its positions (Start, const "XYZ" and End) in out parameters.
// If not found, out parameters get a good position to insert such a statement.
var
  MainBeginNode: TCodeTreeNode;
  AppPos, FirstAppPos: Integer;
  Last: TCommonAtomFlag;
begin
  Result:=false;
  StartPos:=-1;
  ConstStartPos:=-1;
  EndPos:=-1;
  FirstAppPos:=-1;
  BuildTree(lsrEnd);
  MainBeginNode:=FindMainBeginEndNode;
  if (MainBeginNode=nil) or (MainBeginNode.StartPos<1) then exit;
  MoveCursorToCleanPos(MainBeginNode.StartPos);
  repeat
    Last:=CurPos.Flag;
    ReadNextAtom;
    case CurPos.Flag of
    cafWord:
      if UpAtomIs('APPLICATION')
        and ((Last=cafSemicolon)
          or ((Last=cafWord) and LastAtomIs(1,'begin'))) then
      begin
        AppPos:=CurPos.StartPos;
        if FirstAppPos=-1 then
          FirstAppPos:=AppPos;
        ReadNextAtom;
        if CurPos.Flag=cafPoint then
        begin
          if ReadNextUpAtomIs(APropertyUpCase) and ReadNextUpAtomIs(':=') then
          begin
            // Found Application.APropertyUpCase:=
            StartPos:=AppPos;
            ReadNextAtom;
            ConstStartPos:=CurPos.StartPos;
            // find end of assignment
            ReadTilStatementEnd(true,false);
            EndPos:=CurPos.EndPos;
            exit(true);
          end;
        end
        else // Application:=TMyApplication.Create(nil);
        if CurPos.Flag=cafAssignment then begin
          // Application:=
          ReadTilStatementEnd(true,false);
          if CurPos.Flag in [cafEnd,cafSemicolon] then
            StartPos:=CurPos.EndPos;
        end;
      end;
    cafRoundBracketOpen,cafEdgedBracketOpen:
      ReadTilBracketCloseOrUnexpected(true,[]);
    end;  // UpAtomIs('APPLICATION')
  until (CurPos.StartPos>SrcLen);
  // The statement was not found. Return a good place for insertion.
  if StartPos=-1 then begin
    if FirstAppPos>0 then
      StartPos:=FirstAppPos // Before first Application statement if there is one
    else begin
      MoveCursorToNodeStart(MainBeginNode);
      ReadNextAtom;
      StartPos:=CurPos.EndPos; // or after the main Begin.
    end;
  end;
  EndPos:=StartPos;     // Both StartPos and EndPos return the same insert point.
end;

function TStandardCodeTool.GatherResourceStringSections(
  const CursorPos: TCodeXYPosition; PositionList: TCodeXYPositions): boolean;
  
  function SearchInUsesSection(UsesNode: TCodeTreeNode): boolean;
  var
    NewCodeTool: TPascalReaderTool;
    IntfNode: TCodeTreeNode;
    NewCaret: TCodeXYPosition;
    Node: TCodeTreeNode;
    AnUnitName, InFilename: string;
  begin
    Result:=false;
    if UsesNode=nil then exit(true);
    Node:=UsesNode.LastChild;
    while Node<>nil do begin
      AnUnitName:=ExtractUsedUnitName(Node,@InFilename);
      // open the unit
      NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,true);
      NewCodeTool.BuildTree(lsrImplementationStart);
      // search all resource string sections in the interface
      IntfNode:=NewCodeTool.FindInterfaceNode;
      if (IntfNode<>nil) and (IntfNode.LastChild<>nil) then begin
        IntfNode:=IntfNode.LastChild;
        while IntfNode<>nil do begin
          if IntfNode.Desc=ctnResStrSection then begin
            if not NewCodeTool.CleanPosToCaret(IntfNode.StartPos,NewCaret) then
              break;
            //DebugLn('TStandardCodeTool.GatherResourceStringSections Found Other ',NewCodeTool.MainFilename,' Y=',NewCaret.Y);
            PositionList.Add(NewCaret);
          end;
          IntfNode:=IntfNode.PriorBrother;
        end;
      end;
      Node:=Node.PriorBrother;
    end;
    Result:=true;
  end;
  
var
  CleanCursorPos: integer;
  CursorNode: TCodeTreeNode;
  NewCaret: TCodeXYPosition;
  ANode: TCodeTreeNode;
begin
  Result:=false;
  //DebugLn('TStandardCodeTool.GatherResourceStringSections A ');
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  PositionList.Clear;
  ANode:=CursorNode;
  while ANode<>nil do begin
    case ANode.Desc of
    
    ctnResStrSection:
      begin
        if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
        //DebugLn('TStandardCodeTool.GatherResourceStringSections Found Same Y=',NewCaret.Y);
        PositionList.Add(NewCaret);
      end;
      
    ctnUsesSection:
      if not SearchInUsesSection(ANode) then break;
      
    end;
    
    // go to next node
    if ANode.PriorBrother<>nil then begin
      ANode:=ANode.PriorBrother;
      if (ANode.Desc=ctnInterface) and (ANode.LastChild<>nil) then
        ANode:=ANode.LastChild;
    end else begin
      ANode:=ANode.Parent;
    end;
  end;
  Result:=true;
end;

function TStandardCodeTool.IdentifierExistsInResourceStringSection(
  const CursorPos: TCodeXYPosition; const ResStrIdentifier: string): boolean;
var
  CleanCursorPos: integer;
  ANode: TCodeTreeNode;
begin
  Result:=false;
  if ResStrIdentifier='' then exit;
  // parse source and find clean positions
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  // find resource string section
  ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  if (ANode=nil) then exit;
  ANode:=ANode.GetNodeOfType(ctnResStrSection);
  if ANode=nil then exit;
  // search identifier in section
  ANode:=ANode.FirstChild;
  while ANode<>nil do begin
    if (ANode.Desc=ctnConstDefinition)
    and CompareSrcIdentifiers(ANode.StartPos,PChar(ResStrIdentifier)) then begin
      Result:=true;
      exit;
    end;
    ANode:=ANode.NextBrother;
  end;
end;

function TStandardCodeTool.CreateIdentifierFromStringConst(const StartCursorPos,
  EndCursorPos: TCodeXYPosition; out Identifier: string;
  MaxLen: integer): boolean;
var
  StartPos, EndPos: integer;
  Dummy: Integer;
  IdentStr: String;
  ANode: TCodeTreeNode;
  CodePosInFront: LongInt;
begin
  Result:=false;
  if MaxLen<=0 then exit;
  // parse source and find clean positions
  BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
  Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
  if (Dummy<>0) and (Dummy<>-1) then exit;
  ANode:=FindDeepestNodeAtPos(StartPos,True);
  CodePosInFront:=ANode.StartPos;
  // read string constants and extract identifier characters
  Identifier:='';
  MoveCursorToCleanPos(CodePosInFront);
  repeat
    ReadNextAtom;
    //debugln('TStandardCodeTool.CreateIdentifierFromStringConst Atom=',GetAtom);
    if (CurPos.StartPos>=EndPos) then break;
    if AtomIsStringConstant then begin
      IdentStr:=ExtractIdentCharsFromStringConstant(CurPos.StartPos,
                                     StartPos,EndPos,MaxLen-length(Identifier));
      //debugln('TStandardCodeTool.CreateIdentifierFromStringConst IdentStr=',IdentStr);
      if (IdentStr<>'') then begin
        IdentStr[1]:=UpChars[IdentStr[1]];
        Identifier:=Identifier+IdentStr;
      end;
    end;
  until length(Identifier)>=MaxLen;
  Result:=Identifier<>'';
end;

function TStandardCodeTool.StringConstToFormatString(const StartCursorPos,
  EndCursorPos: TCodeXYPosition;
  out FormatStringConstant, FormatParameters: string;
  out StartInStringConst, EndInStringConst: boolean): boolean;
var
  StartPos,EndPos,Dummy: Integer;
begin
  Result:=false;
  // parse source and find clean positions
  BuildTreeAndGetCleanPos(StartCursorPos,StartPos);
  Dummy:=CaretToCleanPos(EndCursorPos, EndPos);
  if (Dummy<>0) and (Dummy<>-1) then exit;
  Result:=GetStringConstAsFormatString(StartPos,EndPos,FormatStringConstant,
                       FormatParameters,StartInStringConst,EndInStringConst);
end;

function TStandardCodeTool.HasInterfaceRegisterProc: boolean;
var
  ANode: TCodeTreeNode;
begin
  Result:=false;
  ANode:=FindDeclarationNodeInInterface('Register',true);
  if ANode=nil then exit;
  if ANode.Desc=ctnProcedureHead then
    ANode:=Anode.Parent;
  if (ANode.Desc<>ctnProcedure) then exit;
  MoveCursorToNodeStart(ANode);
  if not ReadNextUpAtomIs('PROCEDURE') then exit;
  if not ReadNextUpAtomIs('REGISTER') then exit;
  ReadNextAtom;
  if CurPos.Flag<>cafSemicolon then exit;
  Result:=true;
end;

function TStandardCodeTool.ConvertDelphiToLazarusSource(AddLRSCode: boolean;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  Beauty: TBeautifyCodeOptions;

  function AddModeDelphiDirective: boolean;
  var
    ModeDirectivePos: integer;
    InsertPos: Integer;
  begin
    Result:=false;
    BuildTree(lsrInterfaceStart);
    if not FindModeDirective(false,ModeDirectivePos) then begin
      // add {$MODE Delphi} behind source type
      if Tree.Root=nil then exit;
      MoveCursorToNodeStart(Tree.Root);
      ReadNextAtom; // 'unit', 'program', ..
      ReadNextAtom; // name
      ReadNextAtom; // semicolon
      InsertPos:=CurPos.EndPos;
      SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
        '{$MODE Delphi}');
      if not SourceChangeCache.Apply then exit;
    end;
    // changing mode requires rescan
    BuildTree(lsrImplementationStart);
    Result:=true;
  end;

  function ConvertUsedUnits: boolean;
  // replace unit 'Windows' with 'LCLIntf' and add 'LResources'
  // rename 'in' filenames to case sensitive filename
  var
    NamePos, InPos: TAtomPosition;
  begin
    Result:=false;
    if FindUnitInAllUsesSections('WINDOWS',NamePos,InPos)
    and (InPos.StartPos<1) then begin
      if not SourceChangeCache.Replace(gtNone,gtNone,
                           NamePos.StartPos,NamePos.EndPos,'LCLIntf') then
      begin
        debugln('ConvertUsedUnits Unable to replace Windows with LCLIntf unit');
        exit;
      end;
    end;
    if AddLRSCode then
      if not AddUnitToMainUsesSection('LResources','',SourceChangeCache) then
      begin
        debugln('ConvertUsedUnits Unable to add LResources to main uses section');
        exit;
      end;
    if not RemoveUnitFromAllUsesSections('VARIANTS',SourceChangeCache) then
    begin
      debugln('ConvertUsedUnits Unable to remove Variants from all uses sections');
      exit;
    end;
    if not FixUsedUnitCase(SourceChangeCache) then
    begin
      debugln('ConvertUsedUnits Unable to fix unit filename case sensitivity in all uses sections');
      exit;
    end;
    Result:=true;
  end;

  function RemoveDFMResourceDirective: boolean;
  // remove {$R *.dfm} or {$R *.xfm} directive
  var
    ParamPos: Integer;
    ACleanPos: Integer;
    StartPos: Integer;
    s: String;
  begin
    Result:=false;
    // find $R directive
    ACleanPos:=1;
    repeat
      ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
        Scanner.NestedComments,ParamPos);
      if (ACleanPos<1) or (ACleanPos>SrcLen) or (ParamPos>SrcLen) then break;
      s:=UpperCaseStr(copy(Src,ParamPos,6));
      if (Src[ACleanPos]='{')
      and ((s='*.DFM}') or (s='*.XFM}'))
      then begin
        StartPos:=FindLineEndOrCodeInFrontOfPosition(ACleanPos,true);
        if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,ParamPos+6,'')
        then exit;
        break;
      end;
      ACleanPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments);
    until false;
    Result:=true;
  end;

  function AddLRSIncludeDirective: boolean;
  // add initialization and {$i unit.lrs} include directive
  var
    FirstInclude: TCodeBuffer;
    LRSFilename: String;
    InitializationNode: TCodeTreeNode;
    ImplementationNode: TCodeTreeNode;
    NewCode: String;
    InsertPos: Integer;
    LinkIndex: Integer;
  begin
    Result:=false;
    if AddLRSCode then begin
      LRSFilename:=ExtractFilenameOnly(MainFilename)+'.lrs';
      LinkIndex:=-1;
      FirstInclude:=FindNextIncludeInInitialization(LinkIndex);
      if (FirstInclude<>nil)
      and (CompareFilenames(FirstInclude.Filename,LRSFilename)=0) then begin
        // already there
        Result:=true;
        exit;
      end;
      if Tree.Root.Desc=ctnUnit then begin
        InitializationNode:=FindInitializationNode;
        NewCode:=Beauty.GetIndentStr(Beauty.Indent)
                 +'{$i '+LRSFilename+'}';
        if InitializationNode=nil then begin
          // add also an initialization section
          ImplementationNode:=FindImplementationNode;
          InsertPos:=ImplementationNode.EndPos;
          NewCode:=Beauty.BeautifyKeyWord(
                     'initialization')
                   +Beauty.LineEnd
                   +NewCode;
          if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,
                                           InsertPos,InsertPos,
                                           NewCode) then exit;
        end else begin
          InsertPos:=InitializationNode.StartPos+length('initialization');
          if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
                                           InsertPos,InsertPos,
                                           NewCode) then exit;
        end;
      end else begin
        // only Units supported yet
        exit;
      end;
    end;
    Result:=true;
  end;

begin
  Result:=false;
  if SourceChangeCache=nil then exit;
  SourceChangeCache.MainScanner:=Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  DebugLn('ConvertDelphiToLazarusSource AddModeDelphiDirective');
  if not AddModeDelphiDirective then exit;
  DebugLn('ConvertDelphiToLazarusSource RemoveDFMResourceDirective');
  if not RemoveDFMResourceDirective then exit;
  DebugLn('ConvertDelphiToLazarusSource AddLRSIncludeDirective');
  if not AddLRSIncludeDirective then exit;
  DebugLn('ConvertDelphiToLazarusSource ConvertUsedUnits');
  if not ConvertUsedUnits then exit;
  DebugLn('ConvertDelphiToLazarusSource Apply');
  if not SourceChangeCache.Apply then exit;
  DebugLn('ConvertDelphiToLazarusSource END');
  Result:=true;
end;

function TStandardCodeTool.GetIDEDirectives(DirectiveList: TStrings;
  const Filter: TOnIDEDirectiveFilter): boolean;
var
  StartPos: Integer;
  EndPos: Integer;
begin
  Result:=false;
  DirectiveList.Clear;
  BuildTree(lsrImplementationStart);
  EndPos:=1;
  repeat
    StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
    if StartPos<1 then break;
    EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
    if (Filter=nil) or Filter(Self,StartPos,EndPos) then
      DirectiveList.Add(copy(Src,StartPos,EndPos-StartPos));
    if EndPos>SrcLen then break;
    StartPos:=EndPos;
  until false;
  Result:=true;
end;

function TStandardCodeTool.SetIDEDirectives(DirectiveList: TStrings;
  SourceChangeCache: TSourceChangeCache; const Filter: TOnIDEDirectiveFilter
  ): boolean;
var
  InsertPos: Integer;
  EndPos: Integer;
  StartPos: Integer;
  InsertTxt: String;
  ImplementationNode: TCodeTreeNode;
begin
  Result:=false;
  if SourceChangeCache=nil then exit;
  SourceChangeCache.MainScanner:=Scanner;
  BuildTree(lsrEnd);

  // find first old IDE directive
  InsertPos:=FindNextIDEDirective(Src,1,Scanner.NestedComments);
  if InsertPos>=1 then begin
    EndPos:=FindCommentEnd(Src,InsertPos,Scanner.NestedComments);
    if (Filter<>nil) and (not Filter(Self,InsertPos,EndPos)) then
      InsertPos:=0;
  end else
    InsertPos:=0;

  // remove all old IDE directives
  if InsertPos>=1 then
    EndPos:=InsertPos
  else
    EndPos:=1;
  repeat
    // find next IDE directive
    StartPos:=FindNextIDEDirective(Src,EndPos,Scanner.NestedComments);
    if StartPos<1 then break;
    EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
    if (Filter=nil) or Filter(Self,StartPos,EndPos) then begin
      // remove also space in front of directive
      while (StartPos>1) and (Src[StartPos-1] in [' ',#9]) do dec(StartPos);
      // remove also space behind directive
      while (EndPos<=SrcLen) and (Src[EndPos] in [' ',#9]) do inc(EndPos);
      if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13]) then begin
        inc(EndPos);
        if (EndPos<=SrcLen) and (Src[EndPos] in [#10,#13])
        and (Src[EndPos]<>Src[EndPos-1]) then
          inc(EndPos);
      end;
      // remove directive
      if not SourceChangeCache.Replace(gtNone,gtNone,StartPos,EndPos,'') then
        exit;
    end;
    if EndPos>SrcLen then break;
    StartPos:=EndPos;
  until false;
  
  // find a nice insert position
  ImplementationNode:=FindImplementationNode;
  if (ImplementationNode<>nil)
  and (ImplementationNode.StartPos<=InsertPos) then
    InsertPos:=0;
  if InsertPos<1 then begin
    // set default insert position
    InsertPos:=1;
    if (Tree<>nil) and (Tree.Root<>nil) then
      InsertPos:=Tree.Root.StartPos;
  end;
  
  // add directives
  InsertTxt:=ChompLineEndsAtEnd(DirectiveList.Text);
  if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
                            InsertTxt) then exit;
  if not SourceChangeCache.Apply then exit;

  Result:=true;
end;

procedure TStandardCodeTool.CalcMemSize(Stats: TCTMemStats);
begin
  inherited CalcMemSize(Stats);
end;

function TStandardCodeTool.GatherResourceStringsWithValue(
  const CursorPos: TCodeXYPosition; const StringValue: string;
  PositionList: TCodeXYPositions): boolean;
  
  procedure CompareStringConst(ANode: TCodeTreeNode);
  var
    CurValue: String;
    NewCaret: TCodeXYPosition;
  begin
    MoveCursorToNodeStart(ANode);
    ReadNextAtom; // read identifier
    if not AtomIsIdentifier then exit;
    ReadNextAtom; // read =
    if CurPos.Flag<>cafEqual then exit;
    ReadNextAtom; // read start of string constant
    if not AtomIsStringConstant then exit;
    // extract string constant value
    CurValue:=ReadStringConstantValue(CurPos.StartPos);
    if CurValue<>StringValue then exit;
    // values are the same
    // -> add it to position list
    // get x,y position
    if not CleanPosToCaret(ANode.StartPos,NewCaret) then exit;
    //DebugLn('TStandardCodeTool.GatherResourceStringsWithValue Found ',MainFilename,' Y=',NewCaret.Y);
    PositionList.Add(NewCaret);
  end;
  
var
  CleanCursorPos: integer;
  ANode: TCodeTreeNode;
begin
  Result:=false;
  if PositionList=nil then exit;
  // parse source and find clean positions
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  // find resource string section
  ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  if (ANode=nil) then exit;
  ANode:=ANode.GetNodeOfType(ctnResStrSection);
  if ANode=nil then exit;
  // search identifier in section
  ANode:=ANode.FirstChild;
  while ANode<>nil do begin
    if (ANode.Desc=ctnConstDefinition) then begin
      CompareStringConst(ANode);
    end;
    ANode:=ANode.NextBrother;
  end;
end;

function TStandardCodeTool.GetApplicationScaledStatement(BooleanConstStartPos,
  EndPos: integer; var AScaled: boolean): boolean;
begin
  Result:=false;
  AScaled:=false;
  if (BooleanConstStartPos<1) or (BooleanConstStartPos>SrcLen) then exit;
  MoveCursorToCleanPos(BooleanConstStartPos);
  ReadNextAtom;
  if (EndPos>0) and (CurPos.EndPos>EndPos) then exit;
  if UpAtomIs('TRUE') then
  begin
    AScaled := True;
    Result := True;
  end;
  if UpAtomIs('FALSE') then
  begin
    AScaled := False;
    Result := True;
  end;
end;

function TStandardCodeTool.GatherResourceStringIdents(
  const SectionPos: TCodeXYPosition; var IdentTree: TAVLTree): boolean;
var
  CleanCursorPos: integer;
  ANode: TCodeTreeNode;
begin
  Result:=false;
  IdentTree:=nil;
  // parse source and find clean positions
  BuildTreeAndGetCleanPos(SectionPos,CleanCursorPos);
  // find resource string section
  ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  if (ANode=nil) then exit;
  ANode:=ANode.GetNodeOfType(ctnResStrSection);
  if ANode=nil then exit;
  // search identifier in section
  ANode:=ANode.FirstChild;
  while ANode<>nil do begin
    if (ANode.Desc=ctnConstDefinition) then begin
      if IdentTree=nil then
        IdentTree:=TAVLTree.Create(TListSortCompare(@BasicCodeTools.CompareIdentifiers));
      IdentTree.Add(@Src[ANode.StartPos]);
    end;
    ANode:=ANode.NextBrother;
  end;
  Result:=true;
end;

function TStandardCodeTool.FindNearestResourceString(const CursorPos,
  SectionPos: TCodeXYPosition; var NearestPos: TCodeXYPosition): boolean;
var
  CursorTool, SectionTool: TStandardCodeTool;
  IdentTree: TAVLTree;
  NearestNode: TAVLTreeNode;
  NearestCleanPos: Integer;
begin
  Result:=false;
  NearestPos.Code:=nil;
  // get both codetools
  if not Assigned(OnGetCodeToolForBuffer) then exit;
  CursorTool:=
            TStandardCodeTool(OnGetCodeToolForBuffer(Self,CursorPos.Code,true));
  SectionTool:=
           TStandardCodeTool(OnGetCodeToolForBuffer(Self,SectionPos.Code,true));
  if (CursorTool=nil) or (SectionTool=nil) then exit;
  // get all resourcestring identifiers
  IdentTree:=nil;
  Result:=SectionTool.GatherResourceStringIdents(SectionPos,IdentTree);
  if IdentTree=nil then exit;
  try
    // find nearest resourcestring identifier in the cursor source
    NearestNode:=CursorTool.FindNearestIdentifierNode(CursorPos,IdentTree);
    if NearestNode=nil then exit;
    // convert node to cleanpos
    NearestCleanPos:={%H-}PtrUInt(NearestNode.Data)-{%H-}PtrUInt(@SectionTool.Src[1])+1;
    // convert cleanpos to caret
    CleanPosToCaret(NearestCleanPos,NearestPos);
  finally
    IdentTree.Free;
  end;
  Result:=true;
end;

function TStandardCodeTool.AddResourceString(const SectionPos: TCodeXYPosition;
  const NewIdentifier, NewValue: string;
  InsertPolicy: TResourcestringInsertPolicy;
  const NearestPos: TCodeXYPosition;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  CleanSectionPos: integer;
  ANode, SectionNode: TCodeTreeNode;
  Indent: Integer;
  InsertPos: Integer;
  InsertSrc: String;
  NearestCleanPos: integer;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  //DebugLn('TStandardCodeTool.AddResourcestring A ',NewIdentifier,'=',NewValue,' ');
  if (NewIdentifier='') or (length(NewIdentifier)>255) then exit;
  if SourceChangeCache=nil then exit;
  SourceChangeCache.MainScanner:=Scanner;
  // parse source and find clean positions
  //DebugLn('TStandardCodeTool.AddResourcestring B');
  BuildTreeAndGetCleanPos(SectionPos,CleanSectionPos);
  //DebugLn('TStandardCodeTool.AddResourcestring C');
  // find resource string section
  SectionNode:=FindDeepestNodeAtPos(CleanSectionPos,true);
  if (SectionNode=nil) then exit;
  SectionNode:=SectionNode.GetNodeOfType(ctnResStrSection);
  if SectionNode=nil then exit;

  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  //DebugLn('TStandardCodeTool.AddResourcestring D SectionChilds=',SectionNode.FirstChild<>nil);
  // find insert position
  if SectionNode.FirstChild=nil then begin
    // no resourcestring in this section yet -> append as first child
    Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
    InsertPos:=SectionNode.StartPos+length('RESOURCESTRING');
  end else begin
    // search insert position
    case InsertPolicy of
    rsipAlphabetically:
      begin
        // insert new identifier alphabetically
        ANode:=SectionNode.FirstChild;
        while (ANode<>nil) do begin
          if (ANode.Desc=ctnConstDefinition)
          and (CompareIdentifiers(@Src[ANode.StartPos],
            PChar(Pointer(NewIdentifier)))<0)
          then
            break;
          ANode:=ANode.NextBrother;
        end;
        if ANode=nil then begin
          // append new identifier as last
          Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
          InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
        end else begin
          // insert in front of node
          Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
          InsertPos:=FindLineEndOrCodeInFrontOfPosition(ANode.StartPos);
        end;
      end;
      
    rsipContext:
      begin
        // find nearest
        ANode:=nil;
        if (NearestPos.Code<>nil)
        and (CaretToCleanPos(NearestPos,NearestCleanPos)=0) then begin
          ANode:=SectionNode.FirstChild;
          while (ANode<>nil) do begin
            if (ANode.Desc=ctnConstDefinition)
            and (ANode.StartPos<=NearestCleanPos)
            and (ANode.EndPos>NearestCleanPos)
            then begin
              break;
            end;
            ANode:=ANode.NextBrother;
          end;
        end;
        if ANode=nil then begin
          // append new identifier as last
          Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
          InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
        end else begin
          // insert behind node
          Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
          InsertPos:=FindLineEndOrCodeAfterPosition(ANode.EndPos);
        end;
      end;
      
    else
      begin
        // append new identifier
        Indent:=Beauty.GetLineIndent(Src,SectionNode.LastChild.StartPos);
        InsertPos:=FindLineEndOrCodeAfterPosition(SectionNode.LastChild.EndPos);
      end;
    end;
  end;

  //DebugLn('TStandardCodeTool.AddResourcestring E Indent=',Indent,' InsertPos=',InsertPos,' ',copy(Src,InsertPos-9,8),'|',copy(Src,InsertPos,8));
  // insert
  InsertSrc:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
                     NewIdentifier+' = '+NewValue+';',Indent);
  //DebugLn('TStandardCodeTool.AddResourcestring F "',InsertSrc,'"');
  SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,InsertSrc);
  SourceChangeCache.Apply;
  Result:=true;
  //DebugLn('TStandardCodeTool.AddResourcestring END ',Result);
end;

function TStandardCodeTool.FindPublishedVariable(const AClassName,
  AVarName: string; ExceptionOnClassNotFound: boolean): TCodeTreeNode;
var ClassNode, SectionNode: TCodeTreeNode;
begin
  Result:=nil;
  if (AClassName='') or (length(AClassName)>255) then
    RaiseExceptionFmt(20170421201129,ctsinvalidClassName, [AClassName]);
  if AVarName='' then exit;
  BuildTree(lsrImplementationStart);
  ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
  if ClassNode=nil then begin
    if ExceptionOnClassNotFound then
      RaiseExceptionFmt(20170421201136,ctsclassNotFound, [AClassName])
    else
      exit;
  end;
  SectionNode:=ClassNode.FirstChild;
  while (SectionNode<>nil) do begin
    if SectionNode.Desc=ctnClassPublished then begin
      Result:=SectionNode.FirstChild;
      while Result<>nil do begin
        if (Result.Desc=ctnVarDefinition) then begin
          MoveCursorToNodeStart(Result);
          if ReadNextAtomIsIdentifier(PChar(AVarName)) then
            exit;
        end;
        Result:=Result.NextBrother;
      end;
    end;
    SectionNode:=SectionNode.NextBrother;
  end;
end;

function TStandardCodeTool.GatherPublishedVarTypes(const AClassName: string;
  out VarNameToType: TStringToStringTree): boolean;
var
  ClassNode, SectionNode, Node, VarNode, TypeNode: TCodeTreeNode;
  SimpleTypes: TStringToStringTree;
  VarName, NewType, VarType, CurUnitName: String;
  Params: TFindDeclarationParams;
  aContext: TFindContext;
begin
  Result:=true;
  VarNameToType:=nil;
  // search class
  if (AClassName='') or (length(AClassName)>255) then
    RaiseExceptionFmt(20230411091809,ctsinvalidClassName, [AClassName]);
  BuildTree(lsrImplementationStart);
  ClassNode:=FindClassNodeInInterface(AClassName,true,false,false);
  if ClassNode=nil then
    RaiseExceptionFmt(20230411091811,ctsclassNotFound, [AClassName]);
  // traverse class declaration
  SimpleTypes:=TStringToStringTree.Create(false);
  try
    SectionNode:=ClassNode.FirstChild;
    while (SectionNode<>nil) do begin
      if SectionNode.Desc=ctnClassPublished then begin
        Node:=SectionNode.FirstChild;
        while Node<>nil do begin
          VarNode:=Node;
          Node:=Node.NextBrother;
          if (VarNode.Desc<>ctnVarDefinition) then continue;
          // published variable
          TypeNode:=FindTypeNodeOfDefinition(VarNode);
          if TypeNode=nil then continue;
          if TypeNode.Desc<>ctnIdentifier then continue;
          // read variable name
          VarName:=GetIdentifier(@Src[VarNode.StartPos]);
          //debugln(['TStandardCodeTool.GatherPublishedVarTypes VarName="',VarName,'"']);
          // read variable type
          MoveCursorToNodeStart(TypeNode);
          ReadNextAtom;
          if not AtomIsIdentifier then
            continue;
          VarType:=GetAtom;
          while ReadNextAtomIs('.') do begin
            ReadNextAtom;
            if not AtomIsIdentifier then
              break;
            VarType:=VarType+'.'+GetAtom;
          end;
          //debugln(['TStandardCodeTool.GatherPublishedVarTypes VarType="',VarType,'"']);
          if (CurPos.Flag<>cafSemicolon) then begin
            //debugln(['TStandardCodeTool.GatherPublishedVarTypes WARNING not a simple type: ',VarName]);
            continue; // e.g. specialize A<B>
          end;

          NewType:=SimpleTypes[VarType];
          if NewType='' then
          begin
            // resolve simple type
            Params:=TFindDeclarationParams.Create;
            try
              Params.ContextNode:=TypeNode;
              // resolve alias
              aContext:=FindBaseTypeOfNode(Params,TypeNode);
              //debugln(['TStandardCodeTool.GatherPublishedVarTypes Type "',VarType,'" found at ',FindContextToString(aContext,false)]);
              if aContext.Node.Desc in AllClasses then
                NewType:=aContext.Tool.ExtractClassName(aContext.Node,false)
              else if aContext.Node.Desc in AllPascalTypes then
                NewType:=aContext.Tool.ExtractDefinitionName(aContext.Node)
              else
                NewType:='';
              if IsValidIdent(NewType,true,true) then
              begin
                CurUnitName:=aContext.Tool.GetSourceName(false);
                // unitname/vartype
                NewType:=CurUnitName+'/'+NewType;
              end else begin
                NewType:='';
              end;
              //debugln(['TStandardCodeTool.GatherPublishedVarTypes Resolved: "',VarType,'" = "',NewType,'"']);
              SimpleTypes[VarType]:=NewType;
            finally
              Params.Free;
            end;
          end;
          VarType:=NewType;
          //debugln(['TStandardCodeTool.GatherPublishedVarTypes Added ',VarName,':',VarType]);
          if VarNameToType=nil then
            VarNameToType:=TStringToStringTree.Create(false);
          VarNameToType[VarName]:=VarType;
        end;
      end;
      SectionNode:=SectionNode.NextBrother;
    end;
  finally
    SimpleTypes.Free;
  end;
end;

function TStandardCodeTool.AddPublishedVariable(const AClassName,
  VarName, VarType: string; SourceChangeCache: TSourceChangeCache): boolean;
var ClassNode, SectionNode: TCodeTreeNode;
  Indent, InsertPos: integer;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  if (AClassName='') or (length(AClassName)>255) then
    RaiseExceptionFmt(20170421201143,ctsinvalidClassName2, [AClassName]);
  if (VarName='') or (length(VarName)>255) then
    RaiseExceptionFmt(20170421201152,ctsinvalidVariableName, [VarName]);
  if (VarType='') or (length(VarType)>255) then
    RaiseExceptionFmt(20170421201158,ctsinvalidVariableType, [VarType]);
  if (SourceChangeCache=nil) then
    RaiseException(20170421201203,'missing SourceChangeCache');
  if FindPublishedVariable(AClassName,VarName,true)<>nil then
  begin
    Result:=true;
    exit;
  end;
  ClassNode:=FindClassNodeInInterface(AClassName,true,false,true);
  if ClassNode=nil then
    RaiseExceptionFmt(20170421201208,ctsclassNotFound, [AClassName]);
  SectionNode:=ClassNode.FirstChild;
  if (SectionNode.NextBrother<>nil)
  and (SectionNode.NextBrother.Desc=ctnClassPublished) then
    SectionNode:=SectionNode.NextBrother;
  SourceChangeCache.MainScanner:=Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  if SectionNode.FirstChild<>nil then begin
    Indent:=Beauty.GetLineIndent(Src,SectionNode.FirstChild.StartPos);
  end else begin
    Indent:=Beauty.GetLineIndent(Src,SectionNode.StartPos)+Beauty.Indent;
  end;
  InsertPos:=FindLineEndOrCodeInFrontOfPosition(SectionNode.EndPos);
  SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
          Beauty.BeautifyStatement(VarName+':'+VarType+';',Indent)
       );
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.RemovePublishedVariable(const AClassName,
  AVarName: string; ExceptionOnClassNotFound: boolean;
  SourceChangeCache: TSourceChangeCache): boolean;
var VarNode: TCodeTreeNode;
  FromPos, ToPos: integer;
begin
  Result:=false;
  VarNode:=FindPublishedVariable(AClassName,AVarName,
                                 ExceptionOnClassNotFound);
  if VarNode=nil then exit;
  if (VarNode.PriorBrother<>nil)
  and (VarNode.PriorBrother.Desc=ctnVarDefinition)
  and (VarNode.PriorBrother.FirstChild=nil) then begin
    // variable definition has the form  'PriorVarName, VarName: VarType;'
    // or 'PriorVarName, VarName, NextVarName: VarType'
    // -> delete only ', VarName'
    MoveCursorToNodeStart(VarNode.PriorBrother);
    ReadNextAtom; // read 'PriorVarName'
    ReadNextAtom; // read ','
    FromPos:=CurPos.StartPos;
    ReadNextAtom; // read 'VarName'
    ReadNextAtom; // read ':'
    ToPos:=CurPos.StartPos;
  end else begin
    if VarNode.FirstChild<>nil then begin
      // variable definition has the form  'VarName: VarType;'
      // -> delete whole line
      FromPos:=FindLineEndOrCodeInFrontOfPosition(VarNode.StartPos);
      ToPos:=FindLineEndOrCodeAfterPosition(VarNode.EndPos);
      //debugln(['TStandardCodeTool.RemovePublishedVariable ',dbgstr(copy(Src,FromPos,ToPos-FromPos))]);
    end else begin
      // variable definition has the form  'VarName, NextVarName: VarType;'
      // -> delete only 'VarName, '
      FromPos:=VarNode.StartPos;
      ToPos:=VarNode.NextBrother.StartPos;
    end;
  end;
  SourceChangeCache.MainScanner:=Scanner;
  if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.RenamePublishedVariable(const AClassName,
  AOldVarName: string; const NewVarName, VarType: shortstring;
  ExceptionOnClassNotFound: boolean;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  TypeNode, VarNode: TCodeTreeNode;
  ApplyNeeded: Boolean;
begin
  Result:=false;
  if (NewVarName='') or (VarType='') then exit;
  BuildTree(lsrEnd);
  VarNode:=FindPublishedVariable(AClassName,AOldVarName,
                                 ExceptionOnClassNotFound);
  if VarNode<>nil then begin
    // old variable found
    // check type
    TypeNode:=FindTypeNodeOfDefinition(VarNode);
    MoveCursorToNodeStart(TypeNode);
    ReadNextAtom;
    SourceChangeCache.MainScanner:=Scanner;
    ApplyNeeded:=false;
    if (not AtomIsIdentifier(@VarType[1])) then begin
      // change the type
      ApplyNeeded:=true;
      if not SourceChangeCache.Replace(gtNone,gtNone,
        CurPos.StartPos,CurPos.EndPos,VarType)
      then begin
        RaiseException(20170421201215,'Unable to replace type');
      end;
    end;
    // rename variable in source
    if not ReplaceWord(AOldVarName,NewVarName,false,SourceChangeCache,true)
    then
      exit;
    Result:=(not ApplyNeeded) or SourceChangeCache.Apply;
  end else begin
    // old variable not found -> add it
    Result:=AddPublishedVariable(AClassName,NewVarName,VarType,
                                 SourceChangeCache);
  end;
end;

function TStandardCodeTool.GatherPublishedClassElements(
  const TheClassName: string;
  ExceptionOnClassNotFound, WithVariables, WithMethods, WithProperties,
  WithAncestors: boolean;
  out TreeOfCodeTreeNodeExtension: TAVLTree): boolean;
  
  function Add(AFindContext: PFindContext): boolean;
  var
    ClassNode: TCodeTreeNode;
    CurTool: TFindDeclarationTool;
    SectionNode: TCodeTreeNode;
    ANode: TCodeTreeNode;
    CurProcName: String;
    NewNodeExt: TCodeTreeNodeExtension;
    CurPropName: String;
    CurVarName: String;
  begin
    Result:=false;
    ClassNode:=AFindContext^.Node;
    if (ClassNode=nil)
    or (not (ClassNode.Desc in AllClasses)) then exit;
    CurTool:=AFindContext^.Tool;
    SectionNode:=ClassNode.FirstChild;
    while (SectionNode<>nil) do begin
      if SectionNode.Desc=ctnClassPublished then begin
        ANode:=SectionNode.FirstChild;
        while ANode<>nil do begin
          if (ANode.Desc=ctnProcedure) and WithMethods then begin
            CurProcName:=CurTool.ExtractProcName(ANode,[]);
            {$IFDEF VerboseDanglingComponentEvents}
            debugln('TStandardCodeTool.GatherPublishedClassElements CurProcName="',CurProcName,'"');
            {$ENDIF}
            NewNodeExt:=TCodeTreeNodeExtension.Create;
            with NewNodeExt do begin
              Node:=ANode;
              Txt:=CurProcName;
            end;
            TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
          end
          else if (ANode.Desc=ctnVarDefinition) and WithVariables then begin
            CurVarName:=CurTool.ExtractDefinitionName(ANode);
            NewNodeExt:=TCodeTreeNodeExtension.Create;
            with NewNodeExt do begin
              Node:=ANode;
              Txt:=CurVarName;
            end;
            TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
          end
          else if (ANode.Desc=ctnProperty) and WithProperties then begin
            CurPropName:=CurTool.ExtractPropName(ANode,false);
            NewNodeExt:=TCodeTreeNodeExtension.Create;
            with NewNodeExt do begin
              Node:=ANode;
              Txt:=CurPropName;
            end;
            TreeOfCodeTreeNodeExtension.Add(NewNodeExt);
          end;
          ANode:=ANode.NextBrother;
        end;
      end;
      SectionNode:=SectionNode.NextBrother;
    end;
    Result:=true;
  end;
  
var
  ClassNode: TCodeTreeNode;
  AncestorList: TFPList;// of PFindContext
  i: Integer;
begin
  Result:=false;
  TreeOfCodeTreeNodeExtension:=nil;
  if (TheClassName='') or (length(TheClassName)>255) then
    RaiseExceptionFmt(20170421201221,ctsInvalidClassName, [TheClassName]);
  {$IFDEF VerboseDanglingComponentEvents}
  DebugLn(['TStandardCodeTool.GatherPublishedClassElements BEFORE buildtree']);
  {$ENDIF}
  BuildTree(lsrImplementationStart);
  {$IFDEF VerboseDanglingComponentEvents}
  DebugLn(['TStandardCodeTool.GatherPublishedClassElements after buildtree']);
  {$ENDIF}
  ClassNode:=FindClassNodeInInterface(TheClassName,true,false,
    ExceptionOnClassNotFound);
  if ClassNode=nil then exit;
  AncestorList:=nil;
  try
    if WithAncestors then begin
      if not FindClassAndAncestors(ClassNode,AncestorList,true) then exit;
    end else begin
      AddFindContext(AncestorList,CreateFindContext(Self,ClassNode));
    end;
    TreeOfCodeTreeNodeExtension:=TAVLTree.Create(@CompareCodeTreeNodeExt);
    for i:=0 to AncestorList.Count-1 do begin
      if not Add(PFindContext(AncestorList[i])) then exit;
    end;
    //debugln(['TStandardCodeTool.GatherPublishedClassElements END']);
  finally
    FreeListOfPFindContext(AncestorList);
  end;
  Result:=true;
end;

function TStandardCodeTool.RetypeClassVariables(const AClassName: string;
  ListOfTypes: TStringToStringTree; ExceptionOnClassNotFound: boolean;
  SourceChangeCache: TSourceChangeCache; SearchImplementationToo: boolean): boolean;
var
  ClassNode: TCodeTreeNode;
  Node: TCodeTreeNode;
  TypeNode: TCodeTreeNode;
  OldType: String;
  NewType: string;
  HasChanged: Boolean;
begin
  Result:=false;
  if SearchImplementationToo then begin
    BuildTree(lsrEnd);
    ClassNode:=FindClassNodeInUnit(AClassName,true,false,false,
                                   ExceptionOnClassNotFound)
  end
  else begin
    BuildTree(lsrImplementationStart);
    ClassNode:=FindClassNodeInInterface(AClassName,true,false,
                                        ExceptionOnClassNotFound);
  end;
  if ClassNode=nil then exit;
  if (ListOfTypes=nil) or (ListOfTypes.Tree.Count=0) then exit(true);

  HasChanged:=false;
  Node:=ClassNode.FirstChild;
  while (Node<>nil) and (Node.HasAsParent(ClassNode)) do begin
    if (Node.Desc=ctnVarDefinition) and (Node.FirstChild<>nil) then begin
      TypeNode:=Node.FirstChild;
      if TypeNode.Desc=ctnIdentifier then begin
        MoveCursorToNodeStart(TypeNode);
        ReadNextAtom;
        ReadNextAtom;
        if CurPos.Flag=cafPoint then begin
          // skip unitname
          ReadNextAtom;
        end else begin
          UndoReadNextAtom;
        end;
        // cursor is now on identifier
        OldType:=GetAtom;
        if ListOfTypes.Contains(OldType) then begin
          NewType:=ListOfTypes[OldType];
          if OldType<>NewType then begin
            // change type (or case)
            if not HasChanged then begin
              HasChanged:=true;
              SourceChangeCache.MainScanner:=Scanner;
            end;
            if not SourceChangeCache.Replace(gtNone,gtNone,
              CurPos.StartPos,CurPos.EndPos,NewType)
            then
              exit(false);
          end;
        end;
      end;
      Node:=Node.NextSkipChilds;
    end else
      Node:=Node.Next;
  end;
  if HasChanged then begin
    if not SourceChangeCache.Apply then exit;
  end;
  Result:=true;
end;

function TStandardCodeTool.FindDanglingComponentEvents(
  const TheClassName: string; RootComponent: TComponent;
  ExceptionOnClassNotFound, SearchInAncestors: boolean; out
  ListOfPInstancePropInfo: TFPList;
  const OverrideGetMethodName: TOnGetMethodname): boolean;
var
  PublishedMethods: TAVLTree;
  
  procedure AddDanglingEvent(Instance: TPersistent; PropInfo: PPropInfo);
  var
    NewItem: PInstancePropInfo;
  begin
    New(NewItem);
    NewItem^.Instance:=Instance;
    NewItem^.PropInfo:=PropInfo;
    if ListOfPInstancePropInfo=nil then ListOfPInstancePropInfo:=TFPList.Create;
    ListOfPInstancePropInfo.Add(NewItem);
    {$IFDEF VerboseDanglingComponentEvents}
    debugln('AddDanglingEvent ',DbgSName(Instance),' ',PropInfo^.Name);
    {$ENDIF}
  end;

  procedure CheckMethodsInPersistent(APersistent: TPersistent);
  var
    TypeInfo: PTypeInfo;
    PropInfo: PPropInfo;
    PropList: PPropList;
    CurCount,i: integer;
    PropType: PTypeInfo;
    NodeExt: TCodeTreeNodeExtension;
    CurMethod: TMethod;
    CurMethodName: String;
    ObjValue: TObject;
  begin
    if APersistent=nil then exit;
    {$IFDEF VerboseDanglingComponentEvents}
    debugln('TStandardCodeTool.FindDanglingComponentEvents.CheckMethodsInPersistent Checking ',DbgSName(APersistent));
    {$ENDIF}
    // read all properties and remove doubles
    TypeInfo:=APersistent.ClassInfo;
    // read property count
    CurCount:=GetPropList(TypeInfo,PropList);
    try
      {$IFDEF VerboseDanglingComponentEvents}
      debugln('    UnitName=',GetTypeData(TypeInfo)^.UnitName,' Type=',TypeInfo^.Name,' CurPropCount=',dbgs(CurCount));
      {$ENDIF}
      // read properties
      for i:=0 to CurCount-1 do begin
        PropInfo:=PropList^[i];
        {$IFDEF VerboseDanglingComponentEvents}
        debugln('      Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name);
        {$ENDIF}
        PropType:=PropInfo^.PropType;

        if (PropType^.Kind=tkMethod) then begin
          // RTTI property is method
          // -> search method in source
          CurMethod:=GetMethodProp(APersistent,PropInfo);
          if (CurMethod.Data<>nil) or (CurMethod.Code<>nil) then begin
            if Assigned(OverrideGetMethodName) then
              CurMethodName:=OverrideGetMethodName(CurMethod,RootComponent)
            else
              CurMethodName:=OnGetMethodName(CurMethod,RootComponent);
            {$IFDEF VerboseDanglingComponentEvents}
            debugln('      Persistent ',DbgSName(APersistent),' Property ',PropInfo^.Name,' Type=',PropInfo^.PropType^.Name,' CurMethodName="',CurMethodName,'"');
            {$ENDIF}
            if CurMethodName<>'' then begin
              NodeExt:=FindCodeTreeNodeExt(PublishedMethods,CurMethodName);
              if NodeExt=nil then begin
                // method not found -> dangling event
                AddDanglingEvent(APersistent,PropInfo);
              end;
            end;
          end;
        end else if (PropType^.Kind=tkClass) then begin
          // RTTI property is class instance
          ObjValue := TObject(GetObjectProp(APersistent, PropInfo));
          if ObjValue is TCollection then begin
            // collection

          end else if (ObjValue is TPersistent)
          and (not (ObjValue is TComponent)
               or (csSubComponent in TComponent(ObjValue).ComponentStyle))
          then begin
            // sub persistent (e.g. Canvas.Font)
            //debugln(['CheckMethodsInPersistent sub persistent: ',DbgSName(ObjValue)]);
            CheckMethodsInPersistent(TPersistent(ObjValue));
          end;
        end;
     end;
    finally
      FreeMem(PropList);
    end;
  end;

var
  i: Integer;
  Collector: TComponentChildCollector;
  AllComponents: TFPList;
begin
  PublishedMethods:=nil;
  ListOfPInstancePropInfo:=nil;
  Collector:=nil;
  AllComponents:=nil;
  try
    // search all available published methods
    {$IFDEF VerboseDanglingComponentEvents}
    debugln('TStandardCodeTool.FindDanglingComponentEvents A ',MainFilename,' ',DbgSName(RootComponent));
    {$ENDIF}
    Result:=GatherPublishedClassElements(TheClassName,ExceptionOnClassNotFound,
                                         false,true,false,SearchInAncestors,
                                         PublishedMethods);
    if not Result then exit;
    // go through all components
    Collector:=TComponentChildCollector.Create;
    AllComponents:=Collector.GetComponents(RootComponent,true);
    for i:=0 to AllComponents.Count-1 do
      CheckMethodsInPersistent(TComponent(AllComponents[i]));
  finally
    Collector.Free;
    DisposeAVLTree(PublishedMethods);
  end;
end;

function TStandardCodeTool.RemoveIdentifierDefinition(
  const CursorPos: TCodeXYPosition; SourceChangeCache: TSourceChangeCache
  ): boolean;
var
  CleanCursorPos: integer;
  Node: TCodeTreeNode;
  PrevSibling: TCodeTreeNode;
  NextSibling: TCodeTreeNode;
  DeleteStartPos: LongInt;
  DeleteEndPos: LongInt;
  DeleteFirstTokenOfLine: Boolean;
begin
  Result:=false;
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  Node:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
  if Node.Desc in AllIdentifierDefinitions then begin
    // Examples:
    //   var i, X: integer;     ->  var i[, X]: integer;
    //   var i, X, j: integer;  ->  var i, [X, ]j: integer;
    //   var X, i: integer;     ->  var [X, ]i: integer;
    //   type X = integer;
    //   const X = 0;
    //   const X : integer = 0;
    PrevSibling:=nil;
    NextSibling:=nil;
    if (Node.PriorBrother<>nil) and (Node.PriorBrother.FirstChild=nil) then
      PrevSibling:=Node.PriorBrother;
    if (Node.FirstChild=nil) and (Node.NextBrother<>nil) then
      NextSibling:=Node.NextBrother;
    DeleteStartPos:=Node.StartPos;
    DeleteEndPos:=Node.StartPos+GetIdentLen(@Src[Node.StartPos]);
    if NextSibling<>nil then begin
      //   var i, X, j: integer;  ->  var i, [X, ]j: integer;
      //   var X, i: integer;     ->  var [X, ]i: integer;
      MoveCursorToCleanPos(Node.StartPos);
      ReadNextAtom;
      AtomIsIdentifierE;
      if not ReadNextAtomIsChar(',') then
        RaiseCharExpectedButAtomFound(20170421201227,',');
      DeleteEndPos:=CurPos.EndPos;
    end else if PrevSibling<>nil then begin
      // var i, X: integer;     ->  var i[, X]: integer;
      MoveCursorToCleanPos(PrevSibling.StartPos);
      ReadNextAtom;
      AtomIsIdentifierE;
      if not ReadNextAtomIsChar(',') then
        RaiseCharExpectedButAtomFound(20170421201233,',');
      DeleteStartPos:=CurPos.StartPos;
    end else begin
      // delete whole declaration
      if (Node.Parent.Desc in AllDefinitionSections)
      and (Node.PriorBrother=nil) and (Node.NextBrother=nil) then begin
        // delete whole section
        DeleteStartPos:=Node.Parent.StartPos;
        DeleteEndPos:=Node.Parent.EndPos;
      end else if Node.Parent.Desc=ctnParameterList then begin
        // delete whole parameter including modifier, type and default value
        if Node.PriorBrother<>nil then begin
          // ... var i: integer; var X: ... -> ... var i: integer[; var X: ...
          MoveCursorToCleanPos(Node.PriorBrother.EndPos);
          repeat
            ReadNextAtom;
            if CurPos.Flag=cafSemicolon then begin
              DeleteStartPos:=CurPos.EndPos;
              break;
            end;
          until CurPos.StartPos>=Node.StartPos;
        end else begin
          // (var X: ... -> ([; X: ...
          MoveCursorToCleanPos(Node.Parent.StartPos);
          ReadNextAtom;
          if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then
            DeleteStartPos:=CurPos.EndPos;
        end;
        if Node.NextBrother<>nil then begin
          // ... var X: integer; var i: ... -> .. var X: integer;] var i: ...
          DeleteEndPos:=Node.PriorBrother.EndPos;
        end else begin
          // ... var X: integer) -> .. var X: integer])
          DeleteEndPos:=Node.EndPos;
        end;
      end else begin
        // keep section, delete whole declaration
        DeleteEndPos:=Node.EndPos;
      end;
    end;
    // include corresponding comments
    DeleteFirstTokenOfLine:=FindFirstNonSpaceCharInLine(Src,DeleteStartPos)=DeleteStartPos;
    //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition ',dbgstr(copy(Src,FindFirstNonSpaceCharInLine(Src,DeleteStartPos),10))]);
    DeleteEndPos:=FindLineEndOrCodeAfterPosition(DeleteEndPos,true,DeleteFirstTokenOfLine);
    if DeleteFirstTokenOfLine and (Src[DeleteEndPos-1] in [#10,#13]) then begin
      // delete first and last token of line
      // => remove the entire line
      DeleteStartPos:=GetLineStartPosition(Src,DeleteStartPos);
    end;
    //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition "',dbgstr(copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos)),'" IncludeLineEnd=',DeleteFirstTokenOfLine]);

    // delete
    SourceChangeCache.MainScanner:=Scanner;
    if not SourceChangeCache.Replace(gtNone,gtNone,DeleteStartPos,DeleteEndPos,'')
    then exit;
    Result:=SourceChangeCache.Apply;
  end;
end;

function TStandardCodeTool.InsertStatements(
  InsertPos: TInsertStatementPosDescription; Statements: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  CleanCursorPos: integer;
begin
  BeginParsingAndGetCleanPos(lsrEnd,InsertPos.CodeXYPos,CleanCursorPos);
  Result:=InsertStatements(CleanCursorPos,Statements,InsertPos.Indent,
    InsertPos.FrontGap,InsertPos.AfterGap,SourceChangeCache);
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.InsertStatements(CleanPos: integer;
  Statements: string; Indent: integer; FrontGap, AfterGap: TGapTyp;
  SourceChangeCache: TSourceChangeCache): boolean;
{
  ToDo: check for "uses" in Statements and extend uses section
        e.g. "uses unit1, unit2 in 'filename'; statements
  ToDo: check for single statement (e.g. for .. do | dosome;) and add begin/end

 }
var
  Node: TCodeTreeNode;
  SameArea: TAtomPosition;
  BeautifyFlags: TBeautifyCodeFlags;
begin
  Node:=FindDeepestNodeAtPos(CleanPos,true);
  if not (Node.Desc in AllPascalStatements) then begin
    MoveCursorToCleanPos(CleanPos);
    RaiseException(20170421201247,ctsInvalidPositionForInsertionOfStatements);
  end;
  if Node.Desc=ctnBeginBlock then
    Node:=BuildSubTreeAndFindDeepestNodeAtPos(Node,CleanPos,true);

  GetCleanPosInfo(Node.StartPos,CleanPos,false,SameArea);
  if (SameArea.StartPos>SrcLen) or (not IsSpaceChar[Src[SameArea.StartPos]])
  then begin
    MoveCursorToCleanPos(CleanPos);
    RaiseException(20170421201255,ctsInvalidPositionForInsertionOfStatements);
  end;

  SourceChangeCache.MainScanner:=Scanner;
  BeautifyFlags:=[bcfIndentExistingLineBreaks];
  if FrontGap in [gtNone,gtSpace] then
    include(BeautifyFlags,bcfDoNotIndentFirstLine);
  Statements:=SourceChangeCache.BeautifyCodeOptions.BeautifyStatement(
    Statements,Indent,BeautifyFlags);

  Result:=SourceChangeCache.Replace(FrontGap,AfterGap,CleanPos,CleanPos,Statements);
end;

function TStandardCodeTool.FindBlockCounterPart(
  const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
  NewTopLine: integer; IncludeCloseToken: Boolean): boolean;
// jump from bracket-open to bracket-close or 'begin' to 'end'
// or 'until' to 'repeat' ...
var CleanCursorPos: integer;
begin
  Result:=false;
  BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
  // read word at cursor
  MoveCursorToCleanPos(CleanCursorPos);
  if Src[CurPos.StartPos] in ['(','[','{'] then begin
    // jump forward to matching bracket
    ReadNextAtom;
    if not ReadForwardTilAnyBracketClose then exit;
  end else if Src[CurPos.StartPos] in [')',']','}'] then begin
    // jump backward to matching bracket
    ReadNextAtom;
    if not ReadBackwardTilAnyBracketClose then exit;
  end else begin
    if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
    while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
      dec(CurPos.StartPos);
    MoveCursorToCleanPos(CurPos.StartPos);
    ReadNextAtom;
    if CurPos.EndPos=CurPos.StartPos then exit;
    // read till block keyword counterpart
    if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
    or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
      // read forward till END, FINALLY, EXCEPT
      ReadTilBlockEnd(true,false);
    end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
    or UpAtomIs('UNTIL') then
    begin
      // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
      ReadBackTilBlockEnd(true);
    end else
      exit;
  end;
  if IncludeCloseToken then
    ReadNextAtom;
  // CursorPos now contains the counter block keyword
  Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
end;

function TStandardCodeTool.FindBlockStart(const CursorPos: TCodeXYPosition; out
  NewPos: TCodeXYPosition; out NewTopLine: integer; SkipStart: boolean
  ): boolean;
// jump to beginning of current block
// e.g. bracket open, 'begin', 'repeat', ...
var CleanCursorPos: integer;
  CursorOnStart: Boolean;
  Node: TCodeTreeNode;
  MinPos: Integer;
begin
  Result:=false;
  // scan code
  BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
  // read word at cursor
  MoveCursorToCleanPos(CleanCursorPos);
  while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
    dec(CurPos.StartPos);
  MoveCursorToCleanPos(CurPos.StartPos);
  ReadNextAtom;
  try
    if CurPos.StartPos>=SrcLen then begin
      ReadPriorAtom;
      if CurPos.StartPos<1 then begin
        MoveCursorToCleanPos(1);
        exit(true);
      end;
    end;
    Node:=FindDeepestNodeAtPos(CleanCursorPos,false);
    //if Node<>nil then debugln(['TStandardCodeTool.FindBlockStart ',Node.DescAsString]);
    if (Node=nil)
    or (Node.Desc in (AllPascalStatements+AllPascalTypes-AllClasses))
    or (Src[CurPos.StartPos] in [')',']','}'])
    then begin
      MinPos:=1;
      if Node<>nil then MinPos:=Node.StartPos;
      repeat
        //debugln(['TStandardCodeTool.FindBlockStart atom ',CleanPosToStr(CurPos.StartPos),' ',GetAtom]);
        if (CurPos.StartPos<0) then begin
          // start of source found -> this is always a block start
          MoveCursorToCleanPos(1);
          exit(true);
        end
        else if Src[CurPos.StartPos] in [')',']','}'] then begin
          // jump backward to matching bracket
          CursorOnStart:=(CleanCursorPos=CurPos.StartPos);
          if not ReadBackwardTilAnyBracketClose then exit;
          if CursorOnStart then exit(true);
        end
        else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
          CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
        begin
          // block start found
          if (CurPos.StartPos<CleanCursorPos) or (not SkipStart) then
            exit(true);
        end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
        or UpAtomIs('UNTIL') then
        begin
          // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
          CursorOnStart:=(CleanCursorPos>=CurPos.StartPos)
                     and (CleanCursorPos<CurPos.EndPos);
          ReadBackTilBlockEnd(true);
          if CursorOnStart then exit(true);
        end;
        if CurPos.StartPos <= 1 then // Issue #40326
          break;
        ReadPriorAtom;
      until CurPos.StartPos<MinPos;
    end;
    if Node<>nil then begin
      if SkipStart and (CleanCursorPos=Node.StartPos) then begin
        while (Node<>nil) and (Node.StartPos=CleanCursorPos) do
          Node:=Node.Parent;
        if Node<>nil then
          MoveCursorToCleanPos(Node.StartPos)
        else
          MoveCursorToCleanPos(1);
        exit(true);
      end;
      if CleanCursorPos>=Node.StartPos then begin
        MoveCursorToCleanPos(Node.StartPos);
        exit(true);
      end;
    end;
  finally
    if Result then begin
      // CursorPos now contains the block start atom
      Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
    end;
  end;
end;

function TStandardCodeTool.GuessUnclosedBlock(const CursorPos: TCodeXYPosition;
  out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
{ search a block (e.g. begin..end) that looks unclosed, i.e. 'begin'
  without 'end' or 'begin' with 'end' in a different column.
  This function can be used as GuessNextUnclosedBlock, because it ignores blocks
  in front of CursorPos.
  
  Examples for good blocks:
    
    repeat
    until
    
    begin end           // start and end of block in the same line
    
    if expr then begin  // first char in line is relevant, not the block keyword
    end
    
    class;

        
  Examples for bad blocks:
    
    begin               // block start and end has different indenting
      end
      
    asm                 // 'end.' is source end, never asm end
    end.
    
      try               // different indenting
    finally
    
    repeat              // keywords do not match
    end
    
}
var CleanCursorPos: integer;
begin
  Result:=false;
  BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
  // start reading at beginning of code
  MoveCursorToCleanPos(1);
  BuildBlockKeyWordFuncList;
  if ReadTilGuessedUnclosedBlock(CleanCursorPos,false) then
    Result:=CleanPosToCaretAndTopLine(CurPos.StartPos,NewPos,NewTopLine);
  //WriteDebugTreeReport;
end;

function TStandardCodeTool.FindBlockCleanBounds(
  const CursorPos: TCodeXYPosition; out BlockCleanStart, BlockCleanEnd: integer
  ): boolean;
var
  CleanCursorPos: integer;
  BlockStartFound: Boolean;
begin
  Result:=false;
  BlockCleanStart:=0;
  BlockCleanEnd:=0;
  // scan code
  BeginParsingAndGetCleanPos(lsrEnd,CursorPos,CleanCursorPos);
  // read word at cursor
  MoveCursorToCleanPos(CleanCursorPos);
  while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
    dec(CurPos.StartPos);
  MoveCursorToCleanPos(CurPos.StartPos);
  ReadNextAtom;
  BlockStartFound:=false;
  repeat
    ReadPriorAtom;
    if (CurPos.StartPos<0) then begin
      // start of source found -> this is always a block start
      MoveCursorToCleanPos(1);
      BlockStartFound:=true;
      break;
    end
    else if Src[CurPos.StartPos] in [')',']','}'] then begin
      // jump backward to matching bracket
      if not ReadBackwardTilAnyBracketClose then exit;
    end
    else if WordIsBlockStatementStart.DoItCaseInsensitive(Src,
      CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
    begin
      // block start found
      BlockStartFound:=true;
      break;
    end else if UpAtomIs('END') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
    or UpAtomIs('UNTIL') then
    begin
      // read backward till BEGIN, CASE, ASM, RECORD, REPEAT
      ReadBackTilBlockEnd(true);
    end;
  until false;
  if not BlockStartFound then exit;
  BlockCleanStart:=CurPos.StartPos;
  
  // read word at cursor
  MoveCursorToCleanPos(BlockCleanStart);
  if Src[CurPos.StartPos] in ['(','[','{'] then begin
    // jump forward to matching bracket
    ReadNextAtom;
    if not ReadForwardTilAnyBracketClose then exit;
  end else begin
    if Src[CurPos.StartPos] in [';','.'] then dec(CurPos.StartPos);
    while (CurPos.StartPos>2) and IsWordChar[Src[CurPos.StartPos-1]] do
      dec(CurPos.StartPos);
    MoveCursorToCleanPos(CurPos.StartPos);
    ReadNextAtom;
    if CurPos.EndPos=CurPos.StartPos then exit;
    // read till block keyword counterpart
    if UpAtomIs('BEGIN') or UpAtomIs('CASE') or UpAtomIs('ASM')
    or UpAtomIs('RECORD') or UpAtomIs('TRY') or UpAtomIs('REPEAT') then begin
      // read forward till END, FINALLY, EXCEPT
      ReadTilBlockEnd(true,false);
    end else
      exit;
  end;
  BlockCleanEnd:=CurPos.StartPos;
  Result:=true;
end;

function TStandardCodeTool.CompleteBlock(const CursorPos: TCodeXYPosition;
  SourceChangeCache: TSourceChangeCache; OnlyIfCursorBlockIndented: boolean;
  out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
{ For example:
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    begin
      |
      ...
  something
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if then begin
      |
      ...
  something
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  begin
    |

  procedure
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  Statements:
    begin: end;
    asm: end;
    try: finally end;
    finally: end;
    except: end;
    repeat: until ;
    case of: end;
    case :: ;
    case else: end;
    (: )
    [: ]

  Types:
    (: )
    [: ]
    record: end;
    class: end;
    object: end;
    interface: end;
}
type
  TBlockType = (
    btNone,
    btBegin,
    btAsm,
    btEdgedBracket,
    btRoundBracket,
    btTry,
    btFinally,
    btExcept,
    btCase,
    btCaseOf,
    btCaseColon,
    btCaseElse,
    btRepeat,
    btIf,
    btIfElse,
    btClass,
    btInterface,
    btObject,
    btRecord
    );
  TBlock = record
    Typ: TBlockType;
    StartPos: integer;
    InnerIndent: integer;
    InnerStartPos: integer;
  end;
  PBlock = ^TBlock;
  TBlockStack = record
    Stack: PBlock;
    Capacity: integer;
    Top: integer;
  end;
  TExBool = (ebNone, ebTrue, ebFalse);
var
  CleanCursorPos: integer;
  StartNode: TCodeTreeNode;
  InternalCursorAtEmptyLine: TExBool;
  Beauty: TBeautifyCodeOptions;

  function CursorAtEmptyLine: Boolean;
  // true if cursor in empty line or at line end in front of an empty line
  var
    p: LongInt;
  begin
    if InternalCursorAtEmptyLine=ebNone then begin
      if (CleanCursorPos>SrcLen) or InEmptyLine(Src,CleanCursorPos) then
        InternalCursorAtEmptyLine:=ebTrue
      else begin
        p:=CleanCursorPos;
        while (p<=SrcLen) do begin
          case Src[p] of
          ' ',#9: inc(p);
          #10,#13:
            begin
              // after cursor the rest of the line is blank
              // check the next line
              inc(p);
              if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
                inc(p);
              if (p>SrcLen) or InEmptyLine(Src,p) then
                InternalCursorAtEmptyLine:=ebTrue
              else
                InternalCursorAtEmptyLine:=ebFalse;
              break;
            end;
          else
            InternalCursorAtEmptyLine:=ebFalse;
            break;
          end;
        end;
      end;
    end;
    Result:=InternalCursorAtEmptyLine=ebTrue;
  end;

  procedure InitStack(out Stack: TBlockStack);
  begin
    FillByte(Stack{%H-},SizeOf(Stack),0);
    Stack.Top:=-1;
  end;

  procedure FreeStack(var Stack: TBlockStack);
  begin
    ReAllocMem(Stack.Stack,0);
    Stack.Capacity:=0;
    Stack.Top:=-1;
  end;

  procedure BeginBlock(var Stack: TBlockStack; Typ: TBlockType;
    StartPos: integer);
  var
    Block: PBlock;
  begin
    inc(Stack.Top);
    if Stack.Top>=Stack.Capacity then begin
      if Stack.Capacity=0 then
        Stack.Capacity:=16
      else
        Stack.Capacity:=Stack.Capacity*2;
      ReAllocMem(Stack.Stack,SizeOf(TBlock)*Stack.Capacity);
    end;
    {$IFDEF VerboseCompleteBlock}
    DebugLn([GetIndentStr(Stack.Top*2),'BeginBlock ',CleanPosToStr(StartPos),' ',GetAtom]);
    {$ENDIF}
    Block:=@Stack.Stack[Stack.Top];
    Block^.Typ:=Typ;
    Block^.StartPos:=StartPos;
    Block^.InnerIndent:=-1;
    Block^.InnerStartPos:=-1;
  end;

  procedure EndBlock(var Stack: TBlockStack);
  begin
    {$IFDEF VerboseCompleteBlock}
    DebugLn([GetIndentStr(Stack.Top*2),'EndBlock ',GetAtom,' ',CleanPosToStr(CurPos.StartPos),', started at ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
    {$ENDIF}
    dec(Stack.Top);
  end;

  function TopBlockType(const Stack: TBlockStack): TBlockType;
  begin
    if Stack.Top>=0 then
      Result:=Stack.Stack[Stack.Top].Typ
    else
      Result:=btNone;
  end;

  function Replace(NewCode: string; FromPos, ToPos, Indent: integer;
    FrontGap, AfterGap: TGapTyp; BeautifyFlags: TBeautifyCodeFlags): boolean;
  var
    p: LongInt;
  begin
    Result:=false;
    if NewCode='' then exit(true);
    // try to avoid changing current line
    if (FrontGap=gtEmptyLine) then begin
      p:=FromPos;
      while (p>1) and (Src[p-1] in [' ',#9]) do dec(p);
      if (p=1) or (Src[p] in [#10,#13]) then begin
        while (p<=SrcLen) and (Src[p] in [' ',#9]) do inc(p);
        if (p>SrcLen) or (Src[p] in [#10,#13]) then begin
          // inserting in an empty line
          inc(p);
          if (p<=SrcLen) and (Src[p] in [#10,#13]) and (Src[p]<>Src[p-1]) then
            inc(p);
          FrontGap:=gtNewLine;
          FromPos:=p;
          if ToPos<FromPos then ToPos:=FromPos;
        end;
      end;
    end;
    // replace trailing spaces
    while (ToPos<=SrcLen) and (Src[ToPos] in [' ',#9]) do inc(ToPos);
    // use existing semicolon
    if (NewCode[length(NewCode)]=';')
    and (ToPos<=SrcLen) and (Src[ToPos]=';') then begin
      AfterGap:=gtNone;
      inc(ToPos);
    end;
    // use existing "else"
    if (NewCode[length(NewCode)]=';') then begin
      MoveCursorToCleanPos(ToPos);
      ReadNextAtom;
      if UpAtomIs('ELSE') then
        NewCode:=copy(NewCode,1,length(NewCode)-1);
    end;

    // adjust indent of first line
    if FrontGap in [gtNone,gtSpace] then begin
      BeautifyFlags:=BeautifyFlags+[bcfDoNotIndentFirstLine];
      NewCode:=Beauty.GetIndentStr(Indent-GetPosInLine(Src,FromPos))+NewCode;
    end;
    // beautify
    NewCode:=Beauty.BeautifyStatement(
                     NewCode,Indent,BeautifyFlags);

    if AfterGap=gtNewLine then begin
      // do not reuse existing newline, but always add newline
      NewCode:=NewCode+Beauty.LineEnd;
      if (ToPos<SrcLen) and (not (Src[ToPos] in [#10,#13])) then
        NewCode:=NewCode+Beauty.GetIndentStr(Beauty.GetLineIndent(Src,ToPos));
      AfterGap:=gtNone;
    end;
    {$IFDEF VerboseCompleteBlock}
    debugln(['Replace Indent=',Indent,' NewCode="',dbgstr(NewCode),'" Replace: InFront="',DbgStr(copy(Src,FromPos-15,15)),'",Replace="',dbgstr(copy(Src,FromPos,ToPos-FromPos)),'",Behind="',dbgstr(copy(Src,ToPos,15)),'" FrontGap=',dbgs(FrontGap),' AfterGap=',dbgs(AfterGap)]);
    {$ENDIF}
    // insert
    if not SourceChangeCache.Replace(FrontGap,AfterGap,
      FromPos,ToPos,NewCode) then exit;
    if not SourceChangeCache.Apply then exit;
    Result:=true;
  end;

  function CompleteStatements(var Stack: TBlockStack): Boolean;
  var
    CursorBlockLvl: Integer; // the stack level of the cursor
    LastPos: Integer;
    LineStart: boolean; // Atom is first atom of a line in cursor block (not in sub block)
    Indent: Integer;
    CursorBlockInnerIndent, CursorBlockOuterIndent: LongInt;
    CursorBlock: TBlock;
    AtomInFrontOfCursor: TAtomPosition;
    BehindCursorBlock: Boolean; // atom is behind cursor block
    InCursorBlock: Boolean;
    NeedCompletion: integer;
    InsertPos: LongInt;
    NewCode: String;
    AfterGap: TGapTyp;
    FrontGap: TGapTyp;
    BeautifyFlags: TBeautifyCodeFlags;
    BehindPos: LongInt;
    CursorInEmptyStatement: Boolean;
    FromPos: LongInt;
    ToPos: LongInt;
    WasInCursorBlock: Boolean;

    function EndBlockIsOk: boolean;
    begin
      //DebugLn(['EndBlockIsOk ']);
      if (NeedCompletion>0) and (CursorBlockLvl>=0)
      and (Stack.Top=CursorBlockLvl)
      and (Beauty.GetLineIndent(Src,CurPos.StartPos)=CursorBlockOuterIndent) then begin
        // cursor block is properly closed => do not complete
        {$IFDEF VerboseCompleteBlock}
        debugln(['EndBlockIsOk cursor block is properly closed at ',CleanPosToStr(CurPos.StartPos)]);
        {$ENDIF}
        NeedCompletion:=0;
      end;
      EndBlock(Stack);
      Result:=true;
      if (not BehindCursorBlock) and (Stack.Top<CursorBlockLvl) then
        BehindCursorBlock:=true;
    end;

    function CloseBrackets: boolean;
    begin
      while TopBlockType(Stack) in [btRoundBracket,btEdgedBracket] do begin
        if not EndBlockIsOk then exit(false);
      end;
      Result:=true;
    end;

    function InsertPosAtCursor: integer;
    begin
      Result:=BasicCodeTools.FindLineEndOrCodeInFrontOfPosition(Src,
                         CurPos.StartPos,CleanCursorPos,Scanner.NestedComments);
    end;

  begin
    Result:=false;
    MoveCursorToNodeStart(StartNode);
    CursorBlockLvl:=-2;
    LastPos:=-1;
    CursorBlockOuterIndent:=0;
    CursorBlockInnerIndent:=0;
    Indent:=0;
    CursorBlock.StartPos:=0;
    BehindCursorBlock:=false;
    NeedCompletion:=0;
    AtomInFrontOfCursor.StartPos:=0;
    CursorInEmptyStatement:=false;
    repeat
      ReadNextAtom;

      //DebugLn(['ReadStatements Atom=',GetAtom,' TopTyp=',ord(TopBlockType(Stack)),' Top=',Stack.Top]);
      if (Stack.Top>=0) and (Stack.Stack[Stack.Top].InnerIndent<0)
      and (not PositionsInSameLine(Src,Stack.Stack[Stack.Top].StartPos,CurPos.StartPos))
      then begin
        // the first atom of this block is on a new line
        Stack.Stack[Stack.Top].InnerIndent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
        Stack.Stack[Stack.Top].InnerStartPos:=CurPos.StartPos;
      end;

      // check if cursor reached
      if (CurPos.StartPos>=CleanCursorPos) and (CursorBlockLvl<0) then begin
        // reached cursor
        CursorBlockLvl:=Stack.Top;
        if CursorBlockLvl<0 then begin
          // cursor outside blocks or on first atom of first block
          {$IFDEF VerboseCompleteBlock}
          DebugLn(['ReadStatements no completion: cursor outside blocks or on first atom of first block ',CleanPosToStr(CurPos.StartPos)]);
          {$ENDIF}
          exit;
        end else begin
          CursorBlock:=Stack.Stack[CursorBlockLvl];
          CursorBlockOuterIndent:=Beauty.GetLineIndent(Src,CursorBlock.StartPos);
          CursorBlockInnerIndent:=Stack.Stack[Stack.Top].InnerIndent;
          if (CursorBlockInnerIndent<=CursorBlockOuterIndent)
          and OnlyIfCursorBlockIndented then begin
            // cursor block not indented
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements no completion: cursor block not indented ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
            {$ENDIF}
            exit;
          end;
          AtomInFrontOfCursor:=LastAtoms.GetPriorAtom;
          {$IFDEF VerboseCompleteBlock}
          DebugLn(['ReadStatements reached cursor: ',CleanPosToStr(CurPos.StartPos),' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' LastAtom=',GetAtom(AtomInFrontOfCursor),' CurAtom=',GetAtom]);
          {$ENDIF}
          if (CurPos.Flag=cafSemicolon)
          and ((AtomInFrontOfCursor.Flag=cafSemicolon)
            or (CursorBlock.StartPos=AtomInFrontOfCursor.StartPos))
          and (FindNextNonSpace(Src,AtomInFrontOfCursor.EndPos)=CurPos.StartPos)
          then begin
            // cursor in empty statement
            CursorInEmptyStatement:=true;
          end;
        end;
        //DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Indent=',CursorBlockIndent]);
      end;

      InCursorBlock:=(CursorBlockLvl>=0) and (CursorBlockLvl=Stack.Top)
                     and (not BehindCursorBlock);
      WasInCursorBlock:=InCursorBlock;

      // check if end of node
      if (CurPos.StartPos>SrcLen) or (CurPos.StartPos>=StartNode.EndPos) then
      begin
        if InCursorBlock and (NeedCompletion=0) then begin
          {$IFDEF VerboseCompleteBlock}
          DebugLn(['ReadStatements NeedCompletion: source end found at ',CleanPosToStr(CurPos.StartPos)]);
          {$ENDIF}
          NeedCompletion:=CleanCursorPos;
        end;
        break;
      end;

      // check if line start vs outer indent
      LineStart:=InCursorBlock and (LastPos>0)
                 and not PositionsInSameLine(Src,LastPos,CurPos.StartPos);
      if LineStart then
        Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
      if LineStart and (NeedCompletion=0) then begin
        // atom is in same block as cursor (not sub block)
        // and first atom of a line
        // => check indent
        //debugln(['CompleteStatements first atom of line in cursor block: ',GetAtom,' Indent=',Indent,' CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
        if (Indent=CursorBlockOuterIndent) then begin
          if (CursorBlockLvl>0)
          and (Stack.Stack[CursorBlockLvl-1].InnerIndent=Indent)
          and (Stack.Stack[CursorBlockLvl-1].InnerStartPos<CurPos.StartPos)
          then begin
            { for example:
                Code;
                begin|
                Code;
            }
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements NeedCompletion: between same indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent,' Parent.InnerStartPos=',CleanPosToStr(Stack.Stack[CursorBlockLvl-1].InnerStartPos)]);
            {$ENDIF}
            NeedCompletion:=InsertPosAtCursor;
          end;
        end else if (Indent<CursorBlockOuterIndent) then begin
          // for example:
          //    begin
          //    Code;
          //  |end;
          //DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorAtEmptyLine=',CursorAtEmptyLine,' CursorInEmptyStatement=',CursorInEmptyStatement]);
          if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
            // for example:
            //    begin
            //      Code;
            //  |end;
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements NeedCompletion: at out indented ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
            {$ENDIF}
            NeedCompletion:=InsertPosAtCursor;
          end else if CursorAtEmptyLine or CursorInEmptyStatement
          or (FindNextNonSpace(Src,CleanCursorPos)=CurPos.StartPos) then begin
            { for example:
                  begin
                  |
                  Code;
                end;

                  begin
                  Code;
                  |
                end;
            }
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements NeedCompletion: at empty line ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' < CursorBlockOuterIndent=',CursorBlockOuterIndent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
            {$ENDIF}
            NeedCompletion:=CleanCursorPos;
          end else begin
            { It needs completion, but where?
              for example:
                begin
                  begin|
                  Code;
                end;
            }
          end;
        end;
      end;

      // check block starts/ends
      case CurPos.Flag of
      cafEnd:
        if (CurPos.EndPos<=SrcLen) and (Src[CurPos.EndPos]='.') then begin
          { end. of source found
            The parsing started in a begin block, valid cases:

              program a;
              begin|
              end.

              implementation
              begin|
              end.
          }
          if (Stack.Top=0) and (TopBlockType(Stack)=btBegin)
          and (StartNode.Desc=ctnBeginBlock)
          and ((StartNode.Parent=nil)
            or (StartNode.Parent.Desc in AllSourceTypes+[ctnInterface,ctnImplementation]))
          then begin
            if not EndBlockIsOk then exit; // close main begin
          end else begin
            // unexpected end of source
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
            {$ENDIF}
            if InCursorBlock and (NeedCompletion=0) then begin
              {$IFDEF VerboseCompleteBlock}
              DebugLn(['ReadStatements NeedCompletion: unexpected end. at ',CleanPosToStr(CurPos.StartPos)]);
              {$ENDIF}
              NeedCompletion:=CleanCursorPos;
            end;
          end;
          break;
        end else begin
          case TopBlockType(Stack) of
          btCaseOf,btCaseElse:
            begin
              if not EndBlockIsOk then exit; // close btCaseOf,btCaseElse
              if not EndBlockIsOk then exit; // close btCase
            end;
          btBegin,btFinally,btExcept,btCase:
            if not EndBlockIsOk then exit;
          btCaseColon,btRepeat:
            begin
              // missing semicolon or until
              DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
              DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
              if InCursorBlock and (NeedCompletion=0) then begin
                {$IFDEF VerboseCompleteBlock}
                DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
                {$ENDIF}
                NeedCompletion:=CleanCursorPos;
              end;
              break;
            end;
          btTry:
            begin
              // missing finally/except
              DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock]);
              DebugLn(['ReadStatements unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
              if InCursorBlock and (NeedCompletion=0) then begin
                {$IFDEF VerboseCompleteBlock}
                DebugLn(['ReadStatements NeedCompletion: unexpected end at ',CleanPosToStr(CurPos.StartPos),': missing finally ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
                {$ENDIF}
                NeedCompletion:=CleanCursorPos;
              end;
              break;
            end;
          btAsm:
            if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]<>'@') then begin
              if not EndBlockIsOk then exit;
            end;
          else
            // missing begin
            exit;
          end;
        end;
      cafEdgedBracketOpen:
        BeginBlock(Stack,btEdgedBracket,CurPos.StartPos);
      cafEdgedBracketClose:
        if TopBlockType(Stack)=btEdgedBracket then begin
          if not EndBlockIsOk then exit;
        end else begin
          // missing [
          exit;
        end;
      cafRoundBracketOpen:
        BeginBlock(Stack,btRoundBracket,CurPos.StartPos);
      cafRoundBracketClose:
        if TopBlockType(Stack)=btRoundBracket then begin
          if not EndBlockIsOk then exit;
        end else begin
          // missing (
          exit;
        end;
      cafColon:
        if TopBlockType(Stack)=btCaseOf then
          BeginBlock(Stack,btCaseColon,CurPos.StartPos);
      cafSemicolon:
        while TopBlockType(Stack)
        in [btCaseColon,btIf,btIfElse,btRoundBracket,btEdgedBracket] do begin
          if not EndBlockIsOk then exit;
        end;
      cafWord:
        if TopBlockType(Stack)<>btAsm then begin
          if UpAtomIs('BEGIN') then
            BeginBlock(Stack,btBegin,CurPos.StartPos)
          else if UpAtomIs('TRY') then
            BeginBlock(Stack,btTry,CurPos.StartPos)
          else if UpAtomIs('FINALLY') then begin
            if TopBlockType(Stack)=btTry then
              if not EndBlockIsOk then exit;
            BeginBlock(Stack,btFinally,CurPos.StartPos)
          end else if UpAtomIs('EXCEPT') then begin
            if TopBlockType(Stack)=btTry then
              if not EndBlockIsOk then exit;
            BeginBlock(Stack,btExcept,CurPos.StartPos)
          end else if UpAtomIs('REPEAT') then
            BeginBlock(Stack,btRepeat,CurPos.StartPos)
          else if UpAtomIs('UNTIL') then begin
            if TopBlockType(Stack)=btRepeat then begin
              if not EndBlockIsOk then exit;
            end else begin
              // until without repeat
              DebugLn(['ReadStatements CursorBlockLvl=',CursorBlockLvl,' Stack.Top=',Stack.Top,' BehindCursorBlock=',BehindCursorBlock,' Block=',ord(TopBlockType(Stack))]);
              DebugLn(['ReadStatements unexpected until at ',CleanPosToStr(CurPos.StartPos)]);
              exit;
            end;
          end else if UpAtomIs('ASM') then begin
            BeginBlock(Stack,btAsm,CurPos.StartPos);
          end else if UpAtomIs('IF') then begin
            BeginBlock(Stack,btIf,CurPos.StartPos);
          end else if UpAtomIs('THEN') then begin
            CloseBrackets;
            if TopBlockType(Stack)=btIf then begin
              Stack.Stack[Stack.Top].InnerIndent:=-1;
              Stack.Stack[Stack.Top].InnerStartPos:=-1;
            end;
          end else if UpAtomIs('CASE') then begin
            BeginBlock(Stack,btCase,CurPos.StartPos)
          end else if UpAtomIs('OF') then begin
            CloseBrackets;
            if TopBlockType(Stack)=btCase then
              BeginBlock(Stack,btCaseOf,CurPos.StartPos);
          end else if UpAtomIs('ELSE') then begin
            CloseBrackets;
            case TopBlockType(Stack) of
            btIf:
              begin
                if not EndBlockIsOk then exit;
                BeginBlock(Stack,btIfElse,CurPos.StartPos);
              end;
            btCaseOf:
              begin
                if not EndBlockIsOk then exit;
                BeginBlock(Stack,btCaseElse,CurPos.StartPos);
              end;
            btBegin:
              begin
                // missing end
                if InCursorBlock and (NeedCompletion=0) then begin
                  {$IFDEF VerboseCompleteBlock}
                  DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing end. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
                  {$ENDIF}
                  NeedCompletion:=InsertPosAtCursor;
                end;
                break;
              end;
            btCaseColon,btRepeat:
              begin
                // missing semicolon
                if InCursorBlock and (NeedCompletion=0) then begin
                  {$IFDEF VerboseCompleteBlock}
                  DebugLn(['ReadStatements NeedCompletion: unexpected else at ',CleanPosToStr(CurPos.StartPos),': missing semicolon or until. block start: ',CleanPosToStr(Stack.Stack[Stack.Top].StartPos)]);
                  {$ENDIF}
                  NeedCompletion:=InsertPosAtCursor;
                end;
                break;
              end;
            end;
          end else if UpAtomIs('PROCEDURE') or UpAtomIs('FUNCTION')
          or UpAtomIs('CONSTRUCTOR') or UpAtomIs('DESTRUCTOR')
          or UpAtomIs('VAR') or UpAtomIs('TYPE') or UpAtomIs('CONST')
          or UpAtomIs('RESOURCESTRING') or UpAtomIs('LABEL') or UpAtomIs('CLASS')
          or UpAtomIs('INITIALIZATION') or UpAtomIs('FINALIZATION')
          then begin
            // unexpected keyword => block not closed
            if InCursorBlock and (NeedCompletion=0) then begin
              {$IFDEF VerboseCompleteBlock}
              DebugLn(['ReadStatements NeedCompletion: unexpected keyword ',GetAtom,' at ',CleanPosToStr(CurPos.StartPos)]);
              {$ENDIF}
              NeedCompletion:=CleanCursorPos;
            end;
            break;
          end;
        end;
      end;

      // check if line start
      if LineStart and WasInCursorBlock and (not BehindCursorBlock) then begin
        // atom is first atom of a line
        // and atom is in same block as cursor (not sub block)
        // (maybe the atom started a new sub block, but it did not close it)
        // => check indent
        //debugln(['CompleteStatements ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent,' CursorBlockOuterIndent=',CursorBlockOuterIndent]);
        if (Indent<CursorBlockInnerIndent) and (NeedCompletion=0) then begin
          if CursorBlockOuterIndent<CursorBlockInnerIndent then begin
            // for example:
            //  begin
            //    Code;
            //    |
            //    Code;
            //  Code;
            //DebugLn(['ReadStatements Indent=',Indent,' < CursorBlockIndent=',CursorBlockIndent]);
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CurPos.StartPos),' Indent=',Indent,' < CursorBlockInnerIndent=',CursorBlockInnerIndent]);
            {$ENDIF}
            NeedCompletion:=InsertPosAtCursor;
          end else begin
            // for example:
            // begin
            // |
            // Code;
            {$IFDEF VerboseCompleteBlock}
            DebugLn(['ReadStatements NeedCompletion: at ',CleanPosToStr(CleanCursorPos),' Indent=',Indent,' CursorBlockInnerIndent=',CursorBlockInnerIndent]);
            {$ENDIF}
            NeedCompletion:=CleanCursorPos;
            // Note: if the end is coming later, NeedCompletion is disabled
          end;
        end;
      end;

      LastPos:=CurPos.StartPos;
    until Stack.Top<0;

    {$IFDEF VerboseCompleteBlock}
    DebugLn(['ReadStatements END Stack.Top=',Stack.Top,' CursorBlockLvl=',CursorBlockLvl,' BehindCursorBlock=',BehindCursorBlock]);
    {$ENDIF}

    if Stack.Top<0 then begin
      // all blocks closed
      {$IFDEF VerboseCompleteBlock}
      if NeedCompletion>0 then
        DebugLn(['ReadStatements all blocks closed: no completion needed']);
      {$ENDIF}
      NeedCompletion:=0;
    end;

    if (NeedCompletion>0) then begin
      InsertPos:=NeedCompletion;
      while (InsertPos>CleanCursorPos) and (IsSpaceChar[Src[InsertPos-1]]) do
        dec(InsertPos);
      Indent:=CursorBlockOuterIndent;

      // check code behind
      BehindPos:=FindNextNonSpace(Src,InsertPos);
      if BehindPos<=SrcLen then begin
        if (not CursorInEmptyStatement)
        and PositionsInSameLine(Src,InsertPos,BehindPos) then begin
          // target line not empty
          {$IFDEF VerboseCompleteBlock}
          DebugLn(['CompleteStatements target line not empty => skip']);
          {$ENDIF}
          exit;
        end;
        if (Beauty.GetLineIndent(Src,BehindPos)>Indent) then begin
          // code behind is more indented
          // for example
          //   repeat
          //   |
          //     DoSomething;
          debugln(['CompleteStatements BehindPos ',dbgstr(copy(Src,BehindPos-8,8)),'|',dbgstr(copy(Src,BehindPos,8))]);
          {$IFDEF VerboseCompleteBlock}
          DebugLn(['CompleteStatements code behind is indented more (Behind=',Beauty.GetLineIndent(Src,BehindPos),' > Indent=',Indent,') => skip']);
          {$ENDIF}
          exit;
        end;
      end;

      NewCode:=';';
      FrontGap:=gtEmptyLine;
      AfterGap:=gtNewLine;
      FromPos:=InsertPos;
      ToPos:=InsertPos;
      BeautifyFlags:=[bcfIndentExistingLineBreaks];
      if CursorInEmptyStatement and (BehindPos<=SrcLen) then begin
        // replace the empty statement
        FrontGap:=gtNewLine;
        ToPos:=BehindPos;
      end;
      case CursorBlock.Typ of
      btBegin,btFinally,btExcept,btAsm,btCaseOf,btCaseElse:
        NewCode:='end'+NewCode;
      btRepeat:
        NewCode:='until '+NewCode;
      btTry:
        NewCode:='finally'+SourceChangeCache.BeautifyCodeOptions.LineEnd
           +'end'+NewCode;
      btCaseColon:
        begin
          FrontGap:=gtNone;
          AfterGap:=gtNone;
        end;
      else
        exit;
      end;
      if (CursorBlockLvl=0) and (AfterGap=gtNewLine) then begin
        // top level => insert empty lines between top level structures
        AfterGap:=gtEmptyLine;
      end;
      if not Replace(NewCode,FromPos,ToPos,Indent,FrontGap,AfterGap,
        BeautifyFlags) then exit;
    end;
    Result:=true;
  end;

  function CompleteClassSection: Boolean;
  {  type
       TMyClass = class
         |
  }
  var
    LastIndent: LongInt;
    Indent: LongInt;
    InsertPos: LongInt;
    NeedCompletion: Integer;
  begin
    Result:=false;
    if CleanCursorPos<StartNode.StartPos then exit;
    LastIndent:=Beauty.GetLineIndent(Src,StartNode.Parent.StartPos);
    MoveCursorToNodeStart(StartNode);
    //debugln(['CompleteClassSection ',dbgstr(copy(Src,StartNode.StartPos-10,10)),'|',dbgstr(copy(Src,StartNode.StartPos,10))]);
    Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
    if Indent<LastIndent then
      LastIndent:=Indent;
    ReadNextAtom;
    NeedCompletion:=0;
    if (CurPos.StartPos>SrcLen) then begin
      { For example:
          TMyClass = class
          <EOF>
      }
      NeedCompletion:=CleanCursorPos;
    end else if CurPos.Flag=cafWord then begin
      if AtomIsIdentifier then begin
        ReadNextAtom;
        if CurPos.Flag=cafEqual then begin
          { For example:
              TMyClass = class

              TIdentifier =
          }
          NeedCompletion:=CleanCursorPos;
        end else
          exit(true);
      end else begin
        Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
        if Indent<LastIndent then begin
          { For example:
                TMyClass = class

              type
          }
          NeedCompletion:=CleanCursorPos;
        end;
      end;
    end else
      exit(true);
    //debugln(['CompleteClassSection NeedCompletion=',NeedCompletion]);
    if NeedCompletion>0 then begin
      InsertPos:=NeedCompletion;
      Result:=Replace('end;',InsertPos,InsertPos,LastIndent,
        gtNewLine,gtEmptyLine,
        [bcfIndentExistingLineBreaks]);
    end else
      Result:=true;
  end;

  function CompleteClassInterface: Boolean;
  {  type
       TMyClass = interface
         |
  }
  var
    LastIndent: LongInt;
    Indent: LongInt;
    InsertPos: LongInt;
  begin
    Result:=false;
    if CleanCursorPos<StartNode.StartPos then exit;
    LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
    MoveCursorToNodeStart(StartNode);
    ReadNextAtom;
    if CleanCursorPos<CurPos.EndPos then exit(true);
    ReadNextAtom;
    if CurPos.Flag=cafEnd then exit(true);
    if CleanCursorPos<=CurPos.StartPos then begin
      Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
      InsertPos:=CleanCursorPos;
      if Indent<LastIndent then begin
        if not Replace('end;',InsertPos,InsertPos,LastIndent,
          gtNewLine,gtEmptyLine,
          [bcfIndentExistingLineBreaks])
        then
          exit;
      end;
    end;
    Result:=true;
  end;

  function CompleteRecord: Boolean;
  {  type
       TMyClass = record
         |
  }
  var
    LastIndent: LongInt;
    Indent: LongInt;
    InsertPos: LongInt;
  begin
    Result:=false;
    if CleanCursorPos<StartNode.StartPos then exit;
    LastIndent:=Beauty.GetLineIndent(Src,StartNode.StartPos);
    MoveCursorToNodeStart(StartNode);
    ReadNextAtom; // record
    if CleanCursorPos<CurPos.EndPos then exit(true);
    ReadNextAtom;
    if CurPos.Flag=cafEnd then exit(true);
    if CleanCursorPos<=CurPos.StartPos then begin
      Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos);
      InsertPos:=CleanCursorPos;
      if Indent<=LastIndent then begin
        if not Replace('end;',InsertPos,InsertPos,LastIndent,
          gtNewLine,gtEmptyLine,
          [bcfIndentExistingLineBreaks])
        then
          exit;
      end;
    end;
    Result:=true;
  end;

var
  Stack: TBlockStack;
  CommentStart, CommentEnd: integer;
begin
  Result:=false;
  NewPos:=CursorPos;
  NewTopLine:=-1;
  BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
                          [btSetIgnoreErrorPos]);
  StartNode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  if CleanPosIsInComment(CleanCursorPos,StartNode.StartPos,CommentStart,CommentEnd) then begin
    {$IFDEF VerboseCompleteBlock}
    debugln(['TStandardCodeTool.CompleteBlock cursor (',CursorPos.Y,',',CursorPos.X,') cleanpos=[',CleanPosToStr(CleanCursorPos),'] in comment Start=[',CleanPosToStr(CommentStart),'] End=[',CleanPosToStr(CommentEnd),']']);
    {$ENDIF}
    exit;
  end;

  InternalCursorAtEmptyLine:=ebNone;
  SourceChangeCache.MainScanner:=Scanner;
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  InitStack(Stack);
  try
    {$IFDEF VerboseCompleteBlock}
    DebugLn(['TStandardCodeTool.CompleteBlock ',StartNode.DescAsString]);
    {$ENDIF}

    if StartNode.Desc in AllPascalStatements then begin
      while (StartNode.Parent<>nil)
      and (StartNode.Parent.Desc in AllPascalStatements) do
        StartNode:=StartNode.Parent;
      if not CompleteStatements(Stack) then exit;
    end
    else if (StartNode.Desc in AllClassSections)
    or ((StartNode.Desc in AllClassSubSections) and (StartNode.Parent.Desc in AllClassSections))
    then begin
      if not CompleteClassSection then exit;
    end
    else if StartNode.Desc in AllClassInterfaces then begin
      if not CompleteClassInterface then exit;
    end
    else if StartNode.Desc=ctnRecordType then begin
      if not CompleteRecord then exit;
    end;
  finally
    FreeStack(Stack);
  end;

  Result:=true;
end;

{$IFDEF GuessMisplacedIfdef}
function TStandardCodeTool.GuessMisplacedIfdefEndif(
  const CursorPos: TCodeXYPosition;
  out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var
  StartCursorPos, EndCursorPos: integer;
  StartCode, EndCode: Pointer;
begin
  Result:=false;
  try
    BeginParsing(lsrEnd);
  except
    // ignore scanner and parser errors
    on e: ELinkScannerError do ;
    on e: ECodeToolError do ;
  end;
  if Scanner<>nil then begin
    CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,StartCursorPos);
    StartCode:=CursorPos.Code;
    Result:=Scanner.GuessMisplacedIfdefEndif(StartCursorPos,StartCode,
                                             EndCursorPos,EndCode);
    if Result then begin
      NewPos.Code:=TCodeBuffer(EndCode);
      NewPos.Code.AbsoluteToLineCol(EndCursorPos,NewPos.Y,NewPos.X);
      if JumpSingleLinePos>0 then begin
        NewTopLine:=NewPos.Y-(VisibleEditorLines*JumpSingleLinePos div 100);
        if NewTopLine<1 then NewTopLine:=1;
      end else
        NewTopLine:=NewPos.Y;
    end;
  end;
end;
{$ENDIF}

function TStandardCodeTool.FindEnclosingIncludeDirective(
  const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
  NewTopLine: integer): boolean;
var
  CleanCursorPos, LinkIndex, NewCleanPos: integer;
begin
  Result:=false;
  NewPos:=CleanCodeXYPosition;
  NewTopLine:=-1;
  try
    BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
                            [btSetIgnoreErrorPos]);
    LinkIndex:=Scanner.LinkIndexAtCleanPos(CleanCursorPos);
    LinkIndex:=Scanner.FindParentLink(LinkIndex);
    if LinkIndex<0 then
      // this is no include file
      exit;
    NewPos.Code:=TCodeBuffer(Scanner.Links[LinkIndex].Code);
    // calculate the directive end bracket
    NewCleanPos:=Scanner.Links[LinkIndex].CleanedPos+Scanner.LinkSize(LinkIndex)-1;
    Result:=CleanPosToCaretAndTopLine(NewCleanPos,NewPos,NewTopLine);
  finally
    ClearIgnoreErrorAfter;
  end;
end;

function TStandardCodeTool.FindModeDirective(DoBuildTree: boolean;
  out ACleanPos: integer): boolean;
var
  ParamPos: Integer;
begin
  Result:=false;
  ACleanPos:=0;
  if DoBuildTree then BuildTree(lsrMainUsesSectionStart);
  ACleanPos:=FindNextCompilerDirectiveWithName(Src,1,'Mode',
                                               Scanner.NestedComments,ParamPos);
  if ParamPos=0 then ;
  Result:=(ACleanPos>0) and (ACleanPos<=SrcLen);
end;

function TStandardCodeTool.FindResourceDirective(DoBuildTree: boolean;
  var ACleanPos: integer; const Filename: string): boolean;
var
  ParamPos: Integer;
  FilenameStartPos: Integer;
  FilenameEndPos: LongInt;
begin
  Result:=false;
  if DoBuildTree then BuildTree(lsrEnd);
  ACleanPos:=1;
  repeat
    ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
      Scanner.NestedComments,ParamPos);
    if ParamPos=0 then ;
    if (ACleanPos<1) or (ACleanPos>SrcLen) then
      exit(false);
    if Filename='' then begin
      // searching any filename -> found
      exit(true);
    end;
    FilenameStartPos:=ACleanPos+length('{$R ');
    FilenameEndPos:=FilenameStartPos;
    while (FilenameEndPos<=SrcLen) and (Src[FilenameEndPos]<>'}') do
      inc(FilenameEndPos);
    if CompareText(PChar(Pointer(Filename)),length(Filename),
                   @Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
                   true,false)=0
    then begin
      // filename found
      exit(true);
    end;
    ACleanPos:=FilenameEndPos+1;
  until ACleanPos>SrcLen;
end;

function TStandardCodeTool.FindResourceDirective(
  const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition;
  out NewTopLine: integer; const Filename: string): boolean;
var
  CleanCursorPos: integer;
begin
  Result:=false;
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  if not FindResourceDirective(false,CleanCursorPos,Filename) then begin
    //DebugLn('TStandardCodeTool.FindResourceDirective resource directive not found');
    exit;
  end;
  Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
end;

function TStandardCodeTool.AddResourceDirective(const Filename: string;
  SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
var
  ANode: TCodeTreeNode;
  Indent: LongInt;
  InsertPos: Integer;
  AddSrc: String;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  BuildTree(lsrEnd);
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  // find an insert position
  ANode:=FindImplementationNode;
  if ANode<>nil then begin
    Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
    InsertPos:=ANode.StartPos+length('implementation');
  end else begin
    ANode:=FindMainBeginEndNode;
    if ANode<>nil then begin
      Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
      InsertPos:=ANode.StartPos;
    end else begin
      ANode:=FindMainUsesNode;
      if ANode<>nil then begin
        Indent:=Beauty.GetLineIndent(Src,ANode.StartPos);
        InsertPos:=ANode.StartPos;
      end else begin
        Indent:=0;
        InsertPos:=1;
      end;
    end;
  end;

  // insert directive
  SourceChangeCache.MainScanner:=Scanner;
  if NewSrc<>'' then
    AddSrc:=NewSrc
  else
    AddSrc:=Beauty.GetIndentStr(Indent)+'{$R '+Filename+'}';
  if not SourceChangeCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,
    AddSrc) then exit;
  if not SourceChangeCache.Apply then exit;

  Result:=true;
end;

function TStandardCodeTool.FindIncludeDirective(DoBuildTree: boolean;
  var ACleanPos: integer; const Filename: string): boolean;
var
  FilenameStartPos: Integer;
  FilenameEndPos: LongInt;
  CommentStart: integer;
  CommentEnd: integer;
begin
  Result:=false;
  if DoBuildTree then BuildTree(lsrEnd);
  ACleanPos:=1;
  repeat
    ACleanPos:=FindNextIncludeDirective(Src,ACleanPos,Scanner.NestedComments,
                       FilenameStartPos,FilenameEndPos,CommentStart,CommentEnd);
    if (ACleanPos<1) or (ACleanPos>SrcLen) then
      exit(false);
    if Filename='' then begin
      // searching any filename -> found
      exit(true);
    end;
    if CompareText(PChar(Pointer(Filename)),length(Filename),
                   @Src[FilenameStartPos],FilenameEndPos-FilenameStartPos,
                   true,false)=0
    then begin
      // filename found
      exit(true);
    end;
    ACleanPos:=FilenameEndPos+1;
  until ACleanPos>SrcLen;
end;

function TStandardCodeTool.FindIncludeDirective(
  const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out
  NewTopLine: integer; const Filename: string): boolean;
var
  CleanCursorPos: integer;
begin
  Result:=false;
  BuildTreeAndGetCleanPos(CursorPos,CleanCursorPos);
  if not FindIncludeDirective(false,CleanCursorPos,Filename) then begin
    //DebugLn('TStandardCodeTool.FindIncludeDirective resource directive not found');
    exit;
  end;
  Result:=CleanPosToCaretAndTopLine(CleanCursorPos,NewPos,NewTopLine);
end;

function TStandardCodeTool.AddIncludeDirectiveForInit(const Filename: string;
  SourceChangeCache: TSourceChangeCache; const NewSrc: string): boolean;
var
  ANode: TCodeTreeNode;
  Indent: LongInt;
  InsertPos: Integer;
  AddSrc: String;
  Beauty: TBeautifyCodeOptions;
begin
  Result:=false;
  BuildTree(lsrEnd);
  Beauty:=SourceChangeCache.BeautifyCodeOptions;
  // find an insert position
  ANode:=FindInitializationNode;
  if ANode<>nil then begin
    Indent:=Beauty.GetLineIndent(Src,ANode.StartPos)+Beauty.Indent;
    InsertPos:=ANode.StartPos+length('initialization');
  end else begin
    ANode:=FindMainBeginEndNode;
    if ANode<>nil then begin
      MoveCursorToNodeStart(ANode);
      ReadNextAtom;
      //debugln(['TStandardCodeTool.AddIncludeDirective ',GetAtom]);
      Indent:=Beauty.GetLineIndent(Src,CurPos.StartPos)+Beauty.Indent;
      InsertPos:=CurPos.EndPos;
    end else begin
      debugln(['TStandardCodeTool.AddIncludeDirective ToDo: add initialization / begin..end']);
      exit;
    end;
  end;

  // insert directive
  SourceChangeCache.MainScanner:=Scanner;
  if NewSrc<>'' then
    AddSrc:=NewSrc
  else
    AddSrc:=Beauty.GetIndentStr(Indent)+'{$I '+Filename+'}';
  if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos,
    AddSrc) then exit;
  if not SourceChangeCache.Apply then exit;

  Result:=true;
end;

function TStandardCodeTool.AddUnitWarnDirective(WarnID, Comment: string;
  TurnOn: boolean; SourceChangeCache: TSourceChangeCache): boolean;
const
  DirectiveFlagValue: array[boolean] of string = ('off','on');
var
  ACleanPos, DirEndPos, InsertStartPos, MaxPos: Integer;
  Node: TCodeTreeNode;
  p, IDStartPos, IDEndPos, ParamPos: PChar;
  NewCode: String;
begin
  Result:=false;
  if WarnID='' then
    raise Exception.Create('TStandardCodeTool.AddUnitWarnDirective missing WarnID');
  InsertStartPos:=0;
  BuildTree(lsrMainUsesSectionStart);
  SourceChangeCache.MainScanner:=Scanner;

  // fix comment
  if Comment<>'' then begin
    for ACleanPos:=1 to length(Comment) do
      if Comment[ACleanPos] in [#0..#8,#11,#12,#14..#31,'{','}'] then
        Comment[ACleanPos]:='?';
    if not (Comment[1] in [' ',#9,#10,#13]) then Comment:=' '+Comment;
  end;

  // insert in front of first node after source name
  Node:=Tree.Root;
  MaxPos:=Node.StartPos;
  if Node.Desc in AllSourceTypes then
    Node:=Node.Next;
  if (Node<>nil) and (Node.Desc=ctnSrcName) then begin
    MaxPos:=Node.EndPos;
    Node:=Node.NextSkipChilds;
  end;
  if Node<>nil then
    MaxPos:=Node.StartPos;
  MaxPos:=FindLineEndOrCodeAfterPosition(MaxPos,true,true);

  // find existing directive for replacement
  ACleanPos:=1;
  repeat
    ACleanPos:=FindNextCompilerDirective(Src,ACleanPos,Scanner.NestedComments);
    if (ACleanPos<1) or (ACleanPos>MaxPos) then
      break;
    DirEndPos:=FindCommentEnd(Src,ACleanPos,Scanner.NestedComments)+1;
    p:=@Src[ACleanPos+2];
    if CompareIdentifiers(p,'warn')=0 then begin
      IDStartPos:=p+4;
      while IDStartPos^ in [' ',#9,#10,#13] do
        inc(IDStartPos);
      IDEndPos:=IDStartPos;
      while IDEndPos^ in ['0'..'9','A'..'Z','a'..'z','_'] do
        inc(IDEndPos);
      if CompareText(PChar(Pointer(WarnID)),length(WarnID),
                     IDStartPos,IDEndPos-IDStartPos,false)=0
      then begin
        // warn directive found
        p:=IDEndPos;
        while p^ in [' ',#9,#10,#13] do
          inc(p);
        ParamPos:=p;
        while p^ in ['+','-','a'..'z','A'..'Z'] do
          inc(p);
        if not SourceChangeCache.Replace(gtSpace,gtNone,
          ParamPos-PChar(Src)+1,p-PChar(Src)+1,DirectiveFlagValue[TurnOn])
        then
          exit;
        Result:=SourceChangeCache.Apply;
        exit;
      end;
    end else if (CompareIdentifiers(p,'i')=0) or (CompareIdentifiers(p,'include')=0)
    then begin
      // insert before include file
      if MaxPos>ACleanPos then MaxPos:=ACleanPos;
      break;
    end;
    ACleanPos:=DirEndPos;
  until ACleanPos>MaxPos;

  // there was no such directive yet -> find nice insert pos
  InsertStartPos:=FindLineEndOrCodeInFrontOfPosition(MaxPos,true,true);
  NewCode:='{$WARN '+WarnID+' '+DirectiveFlagValue[TurnOn]+Comment+'}';
  if not SourceChangeCache.Replace(gtNewLine,gtNewLine,
    InsertStartPos,InsertStartPos,NewCode)
  then
    exit;
  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.FixIncludeFilenames(Code: TCodeBuffer;
  SourceChangeCache: TSourceChangeCache;
  out FoundIncludeFiles: TStrings;
  var MissingIncludeFilesCodeXYPos: TFPList): boolean;
var
  ASource: String;
  
  {procedure WriteMissingIncludeFilesCodeXYPos;
  var
    CodePos: PCodeXYPosition;
    i: Integer;
  begin
    if MissingIncludeFilesCodeXYPos<>nil then begin
      for i:=0 to MissingIncludeFilesCodeXYPos.Count-1 do begin
        CodePos:=PCodeXYPosition(MissingIncludeFilesCodeXYPos[i]);
        DebugLn('TStandardCodeTool.FixMissingUnits ',dbgs(CodePos));
        DebugLn('TStandardCodeTool.FixMissingUnits ',CodePos^.Code.Filename);
        debugln(CodePos^.Code.Filename
             +'('+IntToStr(CodePos^.y)+','+IntToStr(CodePos^.x)+')'
             +' missing include file');
      end;
    end;
  end;}
  
  procedure Add(FilenameSrcPos: integer; const AFilename: string; Found: boolean);
  var
    NewFilename: String;
    p: PCodeXYPosition;
  begin
    if Found then begin
      if FoundIncludeFiles=nil then
        FoundIncludeFiles:=TStringListUTF8Fast.Create;
      NewFilename:=TrimFilename(AFilename);
      if FoundIncludeFiles.IndexOf(NewFilename)<0 then
        FoundIncludeFiles.Add(NewFilename);
    end else begin
      if MissingIncludeFilesCodeXYPos=nil then
        MissingIncludeFilesCodeXYPos:=TFPList.Create;
      New(p);
      p^.Code:=Code;
      Code.AbsoluteToLineCol(FilenameSrcPos,p^.y,p^.x);
      MissingIncludeFilesCodeXYPos.Add(p);
      ///DebugLn('TStandardCodeTool.FixIncludeFilenames.Add "',p^.Code.Filename,'" ',dbgs(p),' X=',dbgs(p^.X),' Y=',dbgs(p^.Y));
      //WriteMissingIncludeFilesCodeXYPos;
    end;
  end;
  
  function SearchIncludeFilename(FilenameSrcPos: integer; const AFilename: string): string;
  var
    AFilePath: String;
    BaseDir: String;
    CurFilename: String;
    IncludePath: String;
    PathDivider: String;
    ACodeBuf: TCodeBuffer;
  begin
    Result:=TrimFilename(AFilename);
    if FilenameIsAbsolute(Result) then begin
      Result:=DirectoryCache.Pool.FindDiskFilename(Result,true);
      Add(FilenameSrcPos,Result,FileExistsCached(Result));
      //DebugLn('SearchIncludeFilename AbsoluteFilename="',Result,'"');
    end else begin
      BaseDir:=ExtractFilePath(MainFilename);
      //DebugLn('SearchIncludeFilename BaseDir="',BaseDir,'"');
      if FilenameIsAbsolute(BaseDir) then begin
        // unit has normal path -> not virtual
        AFilePath:=ExtractFilePath(Result);
        if AFilePath<>'' then begin
          // search relative to unit
          //debugln(['SearchIncludeFilename BaseDir+Result=',BaseDir+Result]);
          CurFilename:=DirectoryCache.Pool.FindDiskFilename(BaseDir+Result,true);
          //debugln(['SearchIncludeFilename DiskFilename=',CurFilename]);
          Result:=CreateRelativePath(CurFilename,BaseDir);
          //debugln(['SearchIncludeFilename RelativeDiskFilename=',Result]);
          if FileExistsCached(CurFilename) then
            Add(FilenameSrcPos,CurFilename,true)
          else
            Add(FilenameSrcPos,Result,false);
          //DebugLn('SearchIncludeFilename relative filename="',CurFilename,'"');
        end else begin
          // search in path
          IncludePath:='';
          PathDivider:=':;';
          if (Scanner.Values<>nil) then begin
            IncludePath:=Scanner.Values.Variables[ExternalMacroStart+'INCPATH'];
            if Scanner.Values.IsDefined('DELPHI') then
              PathDivider:=':'
          end;
          CurFilename:=SearchFileInPath(Result,BaseDir,IncludePath,PathDivider, ctsfcAllCase);
          if CurFilename<>'' then begin
            // found
            Result:=CreateRelativePath(CurFilename,BaseDir);
            Add(FilenameSrcPos,CurFilename,true);
          end else begin
            // not found
            Add(FilenameSrcPos,Result,false);
          end;
          //DebugLn('SearchIncludeFilename search in include path="',IncludePath,'" Result="',Result,'"');
        end;
      end else begin
        // unit has no path -> virtual unit -> search in virtual files
        ACodeBuf:=TCodeBuffer(Scanner.LoadSourceCaseLoUp(Result));
        if ACodeBuf<>nil then begin
          Result:=ACodeBuf.Filename;
          Add(FilenameSrcPos,Result,true);
        end else begin
          Add(FilenameSrcPos,Result,false);
        end;
      end;
    end;
  end;

  procedure FixFilename(StartPos, EndPos: integer);
  var
    OldFilename: String;
    AFilename: String;
  begin
    OldFilename:=GetForcedPathDelims(copy(ASource,StartPos,EndPos-StartPos));
    //DebugLn('FixFilename ',dbgs(StartPos),' ',dbgs(EndPos),' ',OldFilename);
    AFilename:=OldFilename;
    if ExtractFileExt(AFilename)='' then begin
      // add default extension
      if (Scanner.CompilerMode=cmDELPHI) then
        AFilename:=AFilename+'.pas'
      else
        AFilename:=AFilename+'.pp';
    end;
    AFilename:=SearchIncludeFilename(StartPos,AFilename);
    if OldFilename<>AFilename then begin
      DebugLn('TStandardCodeTool.FixIncludeFilenames.FixFilename replacing in '
             +Code.Filename+' include directive "',OldFilename,'" with "',AFilename,'"');
      SourceChangeCache.ReplaceEx(gtNone,gtNone,0,0,Code,StartPos,EndPos,AFilename);
    end;
  end;
  
var
  p: Integer;
  NestedComments: Boolean;
  FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos: integer;
begin
  Result:=false;
  FoundIncludeFiles:=nil;
  if (Scanner=nil) or (Scanner.MainCode=nil) then exit;
  ASource:=Code.Source;
  Scanner.Scan(lsrInit,false);
  SourceChangeCache.MainScanner:=Scanner;
  
  Result:=true;
  NestedComments:=Scanner.NestedComments;
  p:=1;
  repeat
    p:=BasicCodeTools.FindNextIncludeDirective(ASource,p,NestedComments,
              FilenameStartPos, FileNameEndPos, CommentStartPos, CommentEndPos);
    if (p<1) or (p>length(ASource)) then break;
    if (CommentStartPos=0) and (CommentEndPos=0) then ;
    FixFilename(FilenameStartPos,FilenameEndPos);
    p:=FindCommentEnd(ASource,p,NestedComments);
    //DebugLn('TStandardCodeTool.FixIncludeFilenames ',dbgs(p));
  until false;
  //WriteMissingIncludeFilesCodeXYPos;

  Result:=SourceChangeCache.Apply;
end;

function TStandardCodeTool.ReadTilGuessedUnclosedBlock(
  MinCleanPos: integer;  ReadOnlyOneBlock: boolean): boolean;
// returns true if unclosed block found
var BlockType, CurBlockWord: TBlockKeyword;
  BlockStart: integer;
begin
  Result:=false;
  BlockType:=bkwNone;
  BlockStart:=-1;
  // read til this block is closed
  while (CurPos.StartPos<=SrcLen) do begin
    if BlockKeywordFuncList.DoItCaseInsensitive(Src,
      CurPos.StartPos,CurPos.EndPos-CurPos.StartPos) then
    begin
      for CurBlockWord:=Low(TBlockKeyword) to High(TBlockKeyword) do
        if UpAtomIs(BlockKeywords[CurBlockWord]) then
          break;
      if (CurBlockWord=bkwInterface) and (not LastAtomIs(0,'=')) then
        CurBlockWord:=bkwNone;

      if (CurBlockWord=bkwEnd) then begin
        ReadNextAtom;
        if AtomIsChar('.') then begin
          // source end found
          if BlockType in [bkwBegin,bkwNone] then begin
            MoveCursorToCleanPos(SrcLen+1);
            exit;
          end else begin
            MoveCursorToCleanPos(BlockStart);
            Result:=true;
            exit;
          end;
        end else
          UndoReadNextAtom;
      end;
      
      if BlockType=bkwNone then begin
        case CurBlockWord of

        bkwBegin, bkwAsm, bkwRepeat, bkwCase, bkwTry, bkwRecord:
          begin
            BlockType:=CurBlockWord;
            BlockStart:=CurPos.StartPos;
          end;
          
        bkwClass, bkwObject, bkwInterface, bkwDispInterface:
          begin
            ReadNextAtom;
            if AtomIsChar(';') then begin
              // forward class
            end else if ((CurBlockWord=bkwClass) and UpAtomIs('OF')) then begin
              // 'class of'
            end else if ((CurBlockWord=bkwClass)
            and (UpAtomIs('FUNCTION') or UpAtomIs('PROCEDURE'))) then begin
              // 'class procedure'
            end else if ((CurBlockWord=bkwObject) and LastUpAtomIs(0,'OF')) then
            begin
              // or 'of object'
            end else begin
              BlockType:=CurBlockWord;
              BlockStart:=LastAtoms.GetPriorAtom.StartPos;
              // read ancestor list  class(...)
              if CurPos.Flag=cafRoundBracketOpen then begin
                repeat
                  ReadNextAtom;
                  if AtomIsIdentifier then begin
                    ReadNextAtom;
                    if CurPos.Flag=cafPoint then begin
                      ReadNextAtom;
                      AtomIsIdentifierE;
                    end;
                  end;
                  if CurPos.Flag=cafRoundBracketClose then break;
                  if CurPos.Flag<>cafComma then begin
                    exit(false);
                  end;
                until false;
                ReadNextAtom;
              end;
              // a semicolon directly behind the ancestor list ends the class
              if (CurPos.Flag in [cafEnd,cafSemicolon]) then begin
                // class ends
                BlockType:=bkwNone;
              end else begin
                // class continues
                UndoReadNextAtom;
              end;
            end;
          end;

        bkwEnd, bkwUntil:
          begin
            // close block keywords found, but no block was opened
            //  -> unclosed block found
            Result:=true;
            exit;
          end;
          
        end;
      end
      else
      if ((BlockType in [bkwBegin, bkwAsm, bkwCase, bkwRecord, bkwClass,
        bkwObject, bkwFinally, bkwExcept, bkwInterface, bkwDispInterface])
        and (CurBlockWord=bkwEnd))
      or ((BlockType=bkwRepeat) and (CurBlockWord=bkwUntil)) then begin
        // block end found
        if (MinCleanPos<=CurPos.StartPos)
        and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
        then begin
          // different indent -> unclosed block found
          if Beautifier.GetLineIndent(Src,BlockStart)>Beautifier.GetLineIndent(Src,CurPos.StartPos)
          then begin
            // the current block is more indented than the next block
            // -> probably the current block misses a block end
            MoveCursorToCleanPos(BlockStart);
          end;
          Result:=true;
          exit;
        end;
        // end block
        if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
          // the 'end' keyword is the end for the case block and the record block
          UndoReadNextAtom;
        end;
        BlockType:=bkwNone;
        if ReadOnlyOneBlock then break;
      end
      else
      if (BlockType=bkwTry) and (CurBlockWord in [bkwFinally,bkwExcept]) then
      begin
        // try..finally, try..except found
        if (MinCleanPos<=CurPos.StartPos)
        and (Beautifier.GetLineIndent(Src,CurPos.StartPos)<>Beautifier.GetLineIndent(Src,BlockStart))
        then begin
          // different indent -> unclosed block found
          //   probably a block start is missing, so the error position is
          //   here at block end
          Result:=true;
          exit;
        end;
        // change blocktype
        BlockType:=CurBlockWord;
        BlockStart:=CurPos.StartPos;
      end
      else
      if ((BlockType in [bkwBegin,bkwRepeat,bkwTry,bkwFinally,bkwExcept,
          bkwCase])
        and (CurBlockWord in [bkwBegin,bkwRepeat,bkwTry,bkwCase,bkwAsm]))
      or ((BlockType in [bkwClass,bkwInterface,bkwDispInterface,bkwObject,
          bkwRecord])
        and (CurBlockWord in [bkwRecord])) then
      begin
        // sub blockstart found -> read recursively
        Result:=ReadTilGuessedUnclosedBlock(MinCleanPos,true);
        if Result then exit;
      end
      else
      if (BlockType=bkwRecord) and (CurBlockWord=bkwCase) then begin
        // variant record
      end
      else
      if (BlockType=bkwClass) and (CurBlockWord=bkwClass) then begin
        // class method
      end
      else
      begin
        // unexpected keyword found
        if Beautifier.GetLineIndent(Src,BlockStart)>=Beautifier.GetLineIndent(Src,CurPos.StartPos)
        then begin
          // the current block is more or equal indented than the next block
          // -> probably the current block misses a block end
          MoveCursorToCleanPos(BlockStart);
        end;
        Result:=true;
        exit;
      end;
    end;
    ReadNextAtom;
  end;
end;

function TStandardCodeTool.RemoveApplicationScaledStatement(
  SourceChangeCache: TSourceChangeCache): boolean;
begin
  Result := RemoveApplicationStatement('SCALED', SourceChangeCache);
end;

function TStandardCodeTool.RemoveApplicationStatement(const APropertyUpCase: string;
  SourceChangeCache: TSourceChangeCache): boolean;
var
  StartPos, ConstStartPos, EndPos: integer;
  OldExists: Boolean;
  FromPos: Integer;
  ToPos: Integer;
begin
  Result:=false;
  // search old Application.XYZ:= statement
  OldExists:=FindApplicationStatement(APropertyUpCase,StartPos,ConstStartPos,EndPos);
  if not OldExists then
    exit(true);
  // -> delete whole line
  FromPos:=FindLineEndOrCodeInFrontOfPosition(StartPos);
  ToPos:=FindLineEndOrCodeAfterPosition(EndPos);
  SourceChangeCache.MainScanner:=Scanner;
  if not SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,'') then exit;
  if not SourceChangeCache.Apply then exit;
  Result:=true;
end;

function TStandardCodeTool.ReadForwardTilAnyBracketClose: boolean;
// this function reads any bracket
// (the ReadTilBracketClose function reads only brackets in code, not comments)
var OpenBracket: char;
begin
  Result:=false;
  OpenBracket:=Src[CurPos.StartPos];
  if OpenBracket='{' then begin
    // read til end of comment
    MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
    Result:=CurPos.StartPos<=SrcLen;
  end else if OpenBracket='(' then begin
    if (CurPos.StartPos<SrcLen) and (Src[CurPos.StartPos+1]='*') then begin
      // read til end of comment
      MoveCursorToCleanPos(FindCommentEnd(Src,CurPos.StartPos,Scanner.NestedComments));
      Result:=CurPos.StartPos<=SrcLen;
    end else begin
      // round bracket operator
      Result:=ReadTilBracketClose(false);
    end;
  end else if OpenBracket='[' then begin
    Result:=ReadTilBracketClose(false);
  end;
end;

function TStandardCodeTool.ReadBackwardTilAnyBracketClose: boolean;
// this function reads any bracket
// (the ReadBackTilBracketClose function reads only brackets in code,
//  not comments)
var OpenBracket: char;
  CommentLvl: integer;
begin
  Result:=false;
  OpenBracket:=Src[CurPos.StartPos];
  if OpenBracket='}' then begin
    // read backwards til end of comment
    dec(CurPos.StartPos);
    if (CurPos.StartPos>0) and (Src[CurPos.StartPos]=#3) then begin
      // codetools skip comment
      dec(CurPos.StartPos);
      while (CurPos.StartPos>=1) do begin
        if (Src[CurPos.StartPos]=#3) and (CurPos.StartPos>1)
        and (Src[CurPos.StartPos-1]='}') then begin
          dec(CurPos.StartPos,2);
          break;
        end;
        dec(CurPos.StartPos);
      end;
    end else begin
      // pascal comment
      CommentLvl:=1;
      while (CurPos.StartPos>=1) and (CommentLvl>0) do begin
        case Src[CurPos.StartPos] of
        '}': if Scanner.NestedComments then inc(CommentLvl);
        '{':
          if CommentLvl=1 then begin
            Result:=true;
            break;
          end else
            dec(CommentLvl);
        end;
        dec(CurPos.StartPos);
      end;
    end;
  end else if OpenBracket=')' then begin
    if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='*') then begin
      // read til end of comment
      dec(CurPos.StartPos,3);
      while true do begin
        if (CurPos.StartPos>=1)
        and ((Src[CurPos.StartPos+1]='*') and (Src[CurPos.StartPos]='(')) then
        begin
          Result:=true;
          exit;
        end;
        dec(CurPos.StartPos);
      end;
    end else begin
      Result:=ReadBackTilBracketOpen(false);
    end;
  end else if OpenBracket=']' then begin
    Result:=ReadBackTilBracketOpen(false);
  end;
end;

function TStandardCodeTool.Explore(WithStatements: boolean;
  Range: TLinkScannerRange): boolean;
var
  Node: TCodeTreeNode;
begin
  Result:=true;
  BuildTree(Range);
  Node:=Tree.Root;
  while Node<>nil do begin
    case Node.Desc of
    ctnProcedure,ctnProcedureHead:
      BuildSubTreeForProcHead(Node);
    ctnBeginBlock:
      if WithStatements then
        BuildSubTreeForBeginBlock(Node);
    ctnImplementation:
      if ord(Range)<ord(lsrImplementationStart) then exit;
    end;
    Node:=Node.Next;
  end;
end;

function TStandardCodeTool.Explore(WithStatements: boolean;
  OnlyInterface: boolean): boolean;
begin
  if OnlyInterface then
    Result:=Explore(WithStatements,lsrImplementationStart)
  else
    Result:=Explore(WithStatements,lsrEnd);
end;

finalization
  FreeAndNil(BlockKeywordFuncList);

end.


