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