lazarus/components/codetools/identcompletiontool.pas
Martin ed410cb98c Codetools: fix TIdentifierHistoryList.Add
- If an existing item moves to the front (to HistoryIndex=0) then all Indexes between 0 and the old value need updating (the old value became unused, so items above do not need an update).
- If an new item is inserted, all existing items need to be increased.
2023-06-28 19:30:45 +02:00

4718 lines
164 KiB
ObjectPascal

{
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
TIdentCompletionTool enhances the TFindDeclarationTool with the ability
to create lists of valid identifiers at a specific code position.
}
unit IdentCompletionTool;
{$mode objfpc}{$H+}
interface
{$I codetools.inc}
// activate for debug:
// mem check
{ $DEFINE MEM_CHECK}
// verbosity
{ $DEFINE CTDEBUG}
{ $DEFINE ShowFoundIdents}
{ $DEFINE ShowFilteredIdents}
{ $DEFINE ShowHistory}
{ $DEFINE VerboseCodeContext}
{ $DEFINE VerboseICGatherUnitNames}
{ $DEFINE VerboseICGatherKeywords}
uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
Classes, SysUtils, typinfo, crc, Laz_AVL_Tree,
// LazUtils
LazFileUtils, LazDbgLog, AvgLvlTree,
// Codetools
FileProcs, CodeTree, CodeAtom, CodeCache, CustomCodeTool, CodeToolsStrConsts,
KeywordFuncLists, BasicCodeTools, LinkScanner, SourceChanger,
FindDeclarationTool, PascalReaderTool, PascalParserTool, ExprEval;
type
TIdentCompletionTool = class;
TIdentifierHistoryList = class;
//----------------------------------------------------------------------------
// gathered identifier list
TIdentifierCompatibility = (
icompExact,
icompCompatible,
icompUnknown,
icompIncompatible
);
TIdentifierCompatibilities = set of TIdentifierCompatibility;
TIdentListItemFlag = (
iliHasChilds,
iliBaseExprTypeValid,
iliIsFunction,
iliIsFunctionValid,
iliIsAbstractMethod,
iliIsAbstractMethodValid,
iliParamTypeListValid,
iliParamNameListValid,
iliNodeValid,
iliNodeHashValid,
iliNodeGoneWarned,
iliIsConstructor,
iliIsConstructorValid,
iliIsDestructor,
iliIsDestructorValid,
iliKeyword,
iliResultTypeValid,
iliHasIndexValid,
iliHasIndex,
iliHasParamListValid,
iliHasParamList,
iliIsReadOnlyValid,
iliIsReadOnly,
iliHintModifiersValid,
iliIsDeprecated,
iliIsPlatform,
iliIsExperimental,
iliIsUnimplemented,
iliIsLibrary,
iliAtCursor, // the item is the identifier at the completion
iliNeedsAmpersand, //the item has to be prefixed with '&'
iliHasLowerVisibility
);
TIdentListItemFlags = set of TIdentListItemFlag;
{ TIdentifierListSearchItem }
TIdentifierListSearchItem = class
public
Identifier: PChar;
ParamList: string;
function CalcMemSize: PtrUInt;
end;
TIdentifierList = class;
{ TIdentifierListItem }
TIdentifierListItem = class
private
FParamTypeList: string;
FParamNameList: string;
FNode: TCodeTreeNode;
FResultType: string;
FToolNodesDeletedStep: integer;// only valid if iliNodeValid
FNodeStartPos: integer;
FNodeDesc: TCodeTreeNodeDesc;
FNodeHash: Cardinal;
function GetNode: TCodeTreeNode;
function GetParamTypeList: string;
function GetParamNameList: string;
procedure SetNode(const AValue: TCodeTreeNode);
procedure SetParamTypeList(const AValue: string);
procedure SetParamNameList(const AValue: string);
procedure SetResultType(const AValue: string);
public
Compatibility: TIdentifierCompatibility;
HistoryIndex: integer;
Identifier: string;
Level: integer;
Tool: TFindDeclarationTool;
DefaultDesc: TCodeTreeNodeDesc;
Flags: TIdentListItemFlags;
BaseExprType: TExpressionType;
function AsString: string;
procedure BeautifyIdentifier({%H-}IdentList: TIdentifierList); virtual;
function GetDesc: TCodeTreeNodeDesc;
constructor Create(NewCompatibility: TIdentifierCompatibility;
NewHasChilds: boolean; NewHistoryIndex: integer;
NewIdentifier: PChar; NewLevel: integer;
NewNode: TCodeTreeNode; NewTool: TFindDeclarationTool;
NewDefaultDesc: TCodeTreeNodeDesc);
function IsProcNodeWithParams: boolean;
function IsPropertyWithParams: boolean;
function IsPropertyReadOnly: boolean;
function GetHintModifiers: TPascalHintModifiers;
function CheckHasChilds: boolean;
function CanBeAssigned: boolean;
procedure UpdateBaseContext;
function HasChilds: boolean;
function HasIndex: boolean;
function IsFunction: boolean;
function IsConstructor: boolean;
function IsDestructor: boolean;
function IsAbstractMethod: boolean;
function TryIsAbstractMethod: boolean;
procedure Clear;
procedure UnbindNode;
procedure StoreNodeHash;
function RestoreNode: boolean;
function GetNodeHash(ANode: TCodeTreeNode): Cardinal;
function CompareParamList(CompareItem: TIdentifierListItem): integer;
function CompareParamList(CompareItem: TIdentifierListSearchItem): integer;
function CalcMemSize: PtrUInt; virtual;
public
property ParamTypeList: string read GetParamTypeList write SetParamTypeList;
property ParamNameList: string read GetParamNameList write SetParamNameList;
property ResultType: string read FResultType write SetResultType;
property Node: TCodeTreeNode read GetNode write SetNode;
end;
TIdentifierListItemClass = class of TIdentifierListItem;
TUnitNameSpaceIdentifierListItem = class(TIdentifierListItem)
public
FileUnitName: string;
IdentifierStartInUnitName: Integer;
constructor Create(NewCompatibility: TIdentifierCompatibility;
NewHasChilds: boolean; NewHistoryIndex: integer;
NewIdentifier: PChar; NewLevel: integer;
NewNode: TCodeTreeNode; NewTool: TFindDeclarationTool;
NewDefaultDesc: TCodeTreeNodeDesc;
NewFileUnitName: PChar;
NewIdentifierStartInUnitName: Integer);
function CalcMemSize: PtrUInt; override;
end;
TUnitNameSpaceIdentifierListItemClass = class of TUnitNameSpaceIdentifierListItem;
TIdentifierListFlag = (
ilfFilteredListNeedsUpdate,
ilfUsedToolsNeedsUpdate
);
TIdentifierListFlags = set of TIdentifierListFlag;
TIdentifierListContextFlag = (
ilcfStartInStatement, // context starts in statements. e.g. between begin..end
ilcfStartOfStatement, // atom is start of statement. e.g. 'A|:=' or 'A|;', does not check if A can be assigned
ilcfStartOfOperand, // atom is start of an operand. e.g. 'A|.B'
ilcfStartIsSubIdent, // atom in front is point
ilcfNeedsEndSemicolon, // after context a semicolon is needed. e.g. 'A| end'
ilcfNoEndSemicolon, // no semicolon after. E.g. 'A| else'
ilcfNeedsEndComma, // after context a comma is needed. e.g. 'uses sysutil| classes'
ilcfNeedsDo, // after context a 'do' is needed. e.g. 'with Form1| do'
ilcfIsExpression, // is expression part of statement. e.g. 'if expr'
ilcfCanProcDeclaration,// context allows one to declare a procedure/method
ilcfEndOfLine, // atom at end of line
ilcfDontAllowProcedures// context doesn't allow procedures (e.g. in function parameter, after other operator, in if codition etc. - Delphi mode supports assignment of procedures!)
);
TIdentifierListContextFlags = set of TIdentifierListContextFlag;
TIdentComplSortMethod = (icsScopedAlphabetic, icsAlphabetic, icsScopedDeclaration);
TOnGatherUserIdentifiersToFilteredList = procedure(Sender: TIdentifierList;
FilteredList: TFPList; PriorityCount: Integer) of object;
TIdentifierList = class
private
FContext: TFindContext;
FIdentComplIncludeKeywords: Boolean;
FNewMemberVisibility: TCodeTreeNodeDesc;
FContextFlags: TIdentifierListContextFlags;
FOnGatherUserIdentifiersToFilteredList: TOnGatherUserIdentifiersToFilteredList;
FSortForHistory: boolean;
FSortMethodForCompletion: TIdentComplSortMethod;
FStartAtom: TAtomPosition;
FStartAtomBehind: TAtomPosition;
FStartAtomInFront: TAtomPosition;
FStartBracketLvl: integer;
FStartContextPos: TCodeXYPosition;
FCreatedIdentifiers: TFPList; // list of PChar
FFilteredList: TFPList; // list of TIdentifierListItem
FFlags: TIdentifierListFlags;
FHistory: TIdentifierHistoryList;
FItems: TAvlTree; // tree of TIdentifierListItem (completely sorted)
FIdentView: TAVLTree; // tree of TIdentifierListItem sorted for identifiers
FUsedTools: TAVLTree; // tree of TFindDeclarationTool
FIdentSearchItem: TIdentifierListSearchItem;
FPrefix: string;
FStartContext: TFindContext;
FContainsFilter: Boolean;
function CompareIdentListItems({%H-}Tree: TAvlTree; Data1, Data2: Pointer): integer;
procedure SetHistory(const AValue: TIdentifierHistoryList);
procedure SetSortForHistory(AValue: boolean);
procedure SetSortMethodForCompletion(AValue: TIdentComplSortMethod);
procedure UpdateFilteredList;
function GetFilteredItems(Index: integer): TIdentifierListItem;
procedure SetPrefix(const AValue: string);
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Add(NewItem: TIdentifierListItem);
function Count: integer;
function GetFilteredCount: integer;
function HasIdentifier(Identifier: PChar; const ParamList: string): boolean;
function FindIdentifier(Identifier: PChar; const ParamList: string): TIdentifierListItem;
function FindIdentifier(Identifier: PChar; PreferProc: boolean): TIdentifierListItem;
function FindIdentifier(Identifier: PChar): TIdentifierListItem;
function FindCreatedIdentifier(const Ident: string): integer;
function CreateIdentifier(const Ident: string): PChar;
function StartUpAtomInFrontIs(const s: string): boolean;
function StartUpAtomBehindIs(const s: string): boolean;
function CompletePrefix(const OldPrefix: string): string;
function CalcMemSize: PtrUInt;
public
property Context: TFindContext read FContext write FContext;
property ContextFlags: TIdentifierListContextFlags
read FContextFlags write FContextFlags;
property NewMemberVisibility: TCodeTreeNodeDesc // identifier is a class member, e.g. a variable or a procedure name
read FNewMemberVisibility write FNewMemberVisibility;
property FilteredItems[Index: integer]: TIdentifierListItem read GetFilteredItems;
property History: TIdentifierHistoryList read FHistory write SetHistory;
property Prefix: string read FPrefix write SetPrefix;
property SortForHistory: boolean read FSortForHistory write SetSortForHistory;
property SortMethodForCompletion: TIdentComplSortMethod read FSortMethodForCompletion
write SetSortMethodForCompletion;
property StartAtom: TAtomPosition read FStartAtom write FStartAtom;
property StartAtomInFront: TAtomPosition
read FStartAtomInFront write FStartAtomInFront; // in front of variable, not only of identifier
property StartAtomBehind: TAtomPosition
read FStartAtomBehind write FStartAtomBehind; // directly behind
property StartBracketLvl: integer read FStartBracketLvl write FStartBracketLvl;
property StartContext: TFindContext read FStartContext write FStartContext;
property StartContextPos: TCodeXYPosition
read FStartContextPos write FStartContextPos;
property ContainsFilter: Boolean read FContainsFilter write FContainsFilter;
property IdentComplIncludeKeywords: Boolean read FIdentComplIncludeKeywords write FIdentComplIncludeKeywords;
property OnGatherUserIdentifiersToFilteredList: TOnGatherUserIdentifiersToFilteredList
read FOnGatherUserIdentifiersToFilteredList write FOnGatherUserIdentifiersToFilteredList;
end;
//----------------------------------------------------------------------------
// history list
{ TIdentHistListItem }
TIdentHistListItem = class
public
Identifier: string;
NodeDesc: TCodeTreeNodeDesc;
ParamList: string;
HistoryIndex: integer;
function CalcMemSize: PtrUInt;
end;
{ TIdentifierHistoryList }
TIdentifierHistoryList = class
private
FCapacity: integer;
FItems: TAVLTree; // tree of TIdentHistListItem
procedure SetCapacity(const AValue: integer);
function FindItem(NewItem: TIdentifierListItem): TAVLTreeNode;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Add(NewItem: TIdentifierListItem);
function GetHistoryIndex(AnItem: TIdentifierListItem): integer;
function Count: integer;
function CalcMemSize: PtrUInt;
public
property Capacity: integer read FCapacity write SetCapacity;
end;
//----------------------------------------------------------------------------
{ TCodeContextInfoItem }
TCodeContextInfoItem = class
public
Expr: TExpressionType;
// compiler predefined proc
ProcName: string;
Params: TStringList;
ResultType: string;
destructor Destroy; override;
function AsDebugString(WithExpr: boolean): string;
end;
{ TCodeContextInfo }
TCodeContextInfo = class
private
FEndPos: integer;
FItems: TFPList; // list of TCodeContextInfoItem
FParameterIndex: integer;
FProcName: string;
FProcNameAtom: TAtomPosition;
FStartPos: integer;
FTool: TFindDeclarationTool;
function GetItems(Index: integer): TCodeContextInfoItem;
public
constructor Create;
destructor Destroy; override;
function Count: integer;
property Items[Index: integer]: TCodeContextInfoItem read GetItems; default;
function Add(const Context: TExpressionType): integer;
function AddCompilerProc: integer;
procedure Clear;
property Tool: TFindDeclarationTool read FTool write FTool;
property ParameterIndex: integer read FParameterIndex write FParameterIndex;// 1 based
property ProcName: string read FProcName write FProcName;
property ProcNameAtom: TAtomPosition read FProcNameAtom write FProcNameAtom;
property StartPos: integer read FStartPos write FStartPos;// context is valid from StartPos to EndPos
property EndPos: integer read FEndPos write FEndPos;
function CalcMemSize: PtrUInt;
end;
//----------------------------------------------------------------------------
// TIdentCompletionTool
TOnGatherUserIdentifiers = procedure(Sender: TIdentCompletionTool;
const ContextFlags: TIdentifierListContextFlags) of object;
TIdentCompletionTool = class(TFindDeclarationTool)
private
FBeautifier: TBeautifyCodeOptions;
FLastGatheredIdentParent: TCodeTreeNode;
FLastGatheredIdentLevel: integer;
FICTClassAndAncestorsAndExtClassOfHelper: TFPList;// list of PCodeXYPosition
FIDCTFoundPublicProperties: TAVLTree;// tree of PChar (pointing to the
// property names in source)
FIDTFoundMethods: TAVLTree;// tree of TCodeTreeNodeExtension Txt=clean text
FIDTTreeOfUnitFiles: TAVLTree;// tree of TUnitFileInfo
FIDTTreeOfUnitFiles_NamespacePath: string;
FIDTTreeOfUnitFiles_CaseInsensitive: Boolean;
FIDTTreeOfNamespaces: TAVLTree;// tree of TNameSpaceInfo
FOnGatherUserIdentifiers: TOnGatherUserIdentifiers;
procedure AddToTreeOfUnitFileInfo(const AFilename: string);
procedure AddBaseConstant(const BaseName: PChar);
procedure AddBaseType(const BaseName: PChar);
procedure AddCompilerFunction(const AProcName, AParameterList,
AResultType: PChar);
procedure AddCompilerProcedure(const AProcName, AParameterList: PChar);
procedure AddKeyWord(aKeyWord: string);
protected
CurrentIdentifierList: TIdentifierList;
CurrentIdentifierContexts: TCodeContextInfo;
function CollectAllIdentifiers(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
procedure GatherPredefinedIdentifiers(CleanPos: integer;
const Context, GatherContext: TFindContext);
procedure GatherUsefulIdentifiers(CleanPos: integer;
const Context, GatherContext: TFindContext);
procedure GatherUnitnames(const NameSpacePath: string = '');
procedure GatherSourceNames(const Context: TFindContext);
procedure GatherContextKeywords(const Context: TFindContext;
CleanPos: integer; BeautifyCodeOptions: TBeautifyCodeOptions; const GatherContext: TFindContext);
procedure GatherUserIdentifiers(const ContextFlags: TIdentifierListContextFlags);
procedure InitCollectIdentifiers(const CursorPos: TCodeXYPosition;
var IdentifierList: TIdentifierList);
function ParseSourceTillCollectionStart(const CursorPos: TCodeXYPosition;
out CleanCursorPos: integer; out CursorNode: TCodeTreeNode;
out IdentStartPos, IdentEndPos: integer): boolean;
function FindIdentifierStartPos(const CursorPos: TCodeXYPosition
): TCodeXYPosition;
procedure FindCollectionContext(Params: TFindDeclarationParams;
IdentStartPos: integer; CursorNode: TCodeTreeNode;
out ExprType: TExpressionType; out ContextExprStartPos: LongInt;
out StartInSubContext, HasInheritedKeyword: Boolean);
function CollectAllContexts(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
function CollectAttributeConstructors({%H-}Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
procedure AddCollectionContext(Tool: TFindDeclarationTool;
Node: TCodeTreeNode);
function CheckCursorInCompilerDirective(CursorPos: TCodeXYPosition): boolean;
procedure AddCompilerDirectiveMacros(Directive: string);
public
function GatherAvailableUnitNames(const CursorPos: TCodeXYPosition;
var IdentifierList: TIdentifierList): Boolean;
function GatherIdentifiers(const CursorPos: TCodeXYPosition;
var IdentifierList: TIdentifierList): boolean;
function FindCodeContext(const CursorPos: TCodeXYPosition;
out CodeContexts: TCodeContextInfo): boolean;
function FindAbstractMethods(const CursorPos: TCodeXYPosition;
out ListOfPCodeXYPosition: TFPList;
SkipAbstractsInStartClass: boolean = false): boolean;
function GetValuesOfCaseVariable(const CursorPos: TCodeXYPosition;
List: TStrings; WithTypeDefIfScoped: boolean = true): boolean;
property Beautifier: TBeautifyCodeOptions read FBeautifier write FBeautifier;
procedure CalcMemSize(Stats: TCTMemStats); override;
property OnGatherUserIdentifiers: TOnGatherUserIdentifiers read FOnGatherUserIdentifiers write FOnGatherUserIdentifiers;
end;
function dbgs(Flag: TIdentifierListContextFlag): string; overload;
function dbgs(Flags: TIdentifierListContextFlags): string; overload;
var
CIdentifierListItem: TIdentifierListItemClass = TIdentifierListItem;
CUnitNameSpaceIdentifierListItem: TUnitNameSpaceIdentifierListItemClass = TUnitNameSpaceIdentifierListItem;
implementation
const
CompilerFuncHistoryIndex = 10;
CompilerFuncLevel = 10;
function CompareIdentListItemsForIdents(Data1, Data2: Pointer): integer;
var
Item1: TIdentifierListItem absolute Data1;
Item2: TIdentifierListItem absolute Data2;
begin
// sort alpabetically (lower is better)
Result:=CompareIdentifierPtrs(Pointer(Item2.Identifier),Pointer(Item1.Identifier));
if Result<>0 then exit;
// then sort for ParamList (lower is better)
Result:=Item2.CompareParamList(Item1);
end;
function CompareIdentListSearchWithItems(SearchItem, Item: Pointer): integer;
var
TheSearchItem: TIdentifierListSearchItem absolute SearchItem;
TheItem: TIdentifierListItem absolute Item;
begin
// sort alpabetically (lower is better)
Result:=CompareIdentifierPtrs(Pointer(TheItem.Identifier),TheSearchItem.Identifier);
if Result<>0 then exit;
// then sort for ParamList (lower is better)
Result:=TheItem.CompareParamList(TheSearchItem);
end;
function CompareIdentListSearchWithItemsWithoutParams(SearchItem, Item: Pointer): integer;
var
TheSearchItem: TIdentifierListSearchItem absolute SearchItem;
TheItem: TIdentifierListItem absolute Item;
begin
// sort alpabetically (lower is better)
Result:=CompareIdentifierPtrs(Pointer(TheItem.Identifier),TheSearchItem.Identifier);
end;
function CompareIdentHistListItem(Data1, Data2: Pointer): integer;
var
Item1: TIdentHistListItem absolute Data1;
Item2: TIdentHistListItem absolute Data2;
begin
Result:=CompareIdentifiers(PChar(Pointer(Item2.Identifier)),
PChar(Pointer(Item1.Identifier)));
if Result<>0 then exit;
//debugln('CompareIdentHistListItem ',Item2.Identifier,'=',Item1.Identifier);
Result:=CompareIdentifiers(PChar(Pointer(Item2.ParamList)),
PChar(Pointer(Item1.ParamList)));
end;
function CompareIdentItemWithHistListItem(Data1, Data2: Pointer): integer;
var
IdentItem: TIdentifierListItem absolute Data1;
HistItem: TIdentHistListItem absolute Data2;
begin
Result:=CompareIdentifierPtrs(Pointer(HistItem.Identifier),
Pointer(IdentItem.Identifier));
if Result<>0 then exit;
//debugln('CompareIdentItemWithHistListItem ',HistItem.Identifier,'=',GetIdentifier(IdentItem.Identifier));
Result:=SysUtils.CompareText(HistItem.ParamList,IdentItem.ParamTypeList);
end;
function dbgs(Flag: TIdentifierListContextFlag): string;
begin
Result:=GetEnumName(typeinfo(Flag),ord(Flag));
end;
function dbgs(Flags: TIdentifierListContextFlags): string;
var
f: TIdentifierListContextFlag;
begin
Result:='';
for f:=Low(TIdentifierListContextFlag) to High(TIdentifierListContextFlag) do
if f in Flags then begin
if Result<>'' then Result+=',';
Result+=dbgs(f);
end;
Result:='['+Result+']';
end;
{ TUnitNameSpaceIdentifierListItem }
constructor TUnitNameSpaceIdentifierListItem.Create(
NewCompatibility: TIdentifierCompatibility; NewHasChilds: boolean;
NewHistoryIndex: integer; NewIdentifier: PChar; NewLevel: integer;
NewNode: TCodeTreeNode; NewTool: TFindDeclarationTool;
NewDefaultDesc: TCodeTreeNodeDesc; NewFileUnitName: PChar;
NewIdentifierStartInUnitName: Integer);
begin
inherited Create(NewCompatibility, NewHasChilds, NewHistoryIndex,
NewIdentifier, NewLevel, NewNode, NewTool, NewDefaultDesc);
FileUnitName := NewFileUnitName;
IdentifierStartInUnitName := NewIdentifierStartInUnitName;
end;
function TUnitNameSpaceIdentifierListItem.CalcMemSize: PtrUInt;
begin
Result := inherited CalcMemSize
+MemSizeString(FileUnitName);
end;
{ TIdentifierList }
function TIdentifierList.CompareIdentListItems(Tree: TAvlTree; Data1, Data2: Pointer): integer;
var
Item1: TIdentifierListItem absolute Data1;
Item2: TIdentifierListItem absolute Data2;
begin
if SortMethodForCompletion in [icsScopedAlphabetic, icsScopedDeclaration] then begin
// first sort for Compatibility (lower is better)
if ord(Item1.Compatibility)<ord(Item2.Compatibility) then begin
Result:=-1;
exit;
end else if ord(Item1.Compatibility)>ord(Item2.Compatibility) then begin
Result:=1;
exit;
end;
end;
if SortForHistory then begin
// then sort for History (lower is better)
if Item1.HistoryIndex<Item2.HistoryIndex then begin
Result:=-1;
exit;
end else if Item1.HistoryIndex>Item2.HistoryIndex then begin
Result:=1;
exit;
end;
end;
if SortMethodForCompletion in [icsScopedAlphabetic, icsScopedDeclaration] then begin
// then sort for Level (i.e. scope, lower is better)
if Item1.Level<Item2.Level then begin
Result:=-1;
exit;
end else if Item1.Level>Item2.Level then begin
Result:=1;
exit;
end;
end;
if SortMethodForCompletion = icsScopedDeclaration then begin
if (Item1.Node<>nil) and (Item2.Node<>nil) then
begin
if Item1.Node.StartPos<Item2.Node.StartPos then
begin
Result:=-1;
exit;
end else
if Item1.Node.StartPos>Item2.Node.StartPos then
begin
Result:=1;
exit;
end;
end
else
if (Item1.Node<>nil) xor (Item2.Node<>nil) then begin // One node without source pos
if (Item1.Node<>nil) then
Result := 1
else
Result := -1;
exit;
end;
end;
Result:=CompareIdentifierPtrs(Pointer(Item2.Identifier),Pointer(Item1.Identifier));
if Result<>0 then exit;
// then sort for ParamList (lower is better)
Result:=Item2.CompareParamList(Item1);
end;
procedure TIdentifierList.SetSortMethodForCompletion(
AValue: TIdentComplSortMethod);
begin
if FSortMethodForCompletion = AValue then Exit;
FSortMethodForCompletion := AValue;
Clear;
end;
procedure TIdentifierList.SetPrefix(const AValue: string);
begin
if FPrefix=AValue then exit;
FPrefix:=AValue;
Include(FFlags,ilfFilteredListNeedsUpdate);
end;
procedure TIdentifierList.UpdateFilteredList;
var
AnAVLNode: TAvlTreeNode;
CurItem: TIdentifierListItem;
cPriorityCount: Integer;
i: PtrInt;
begin
if not (ilfFilteredListNeedsUpdate in FFlags) then exit;
if FFilteredList=nil then FFilteredList:=TFPList.Create;
FFilteredList.Count:=0;
FFilteredList.Capacity:=FItems.Count;
{$IFDEF CTDEBUG}
DebugLn(['TIdentifierList.UpdateFilteredList Prefix="',Prefix,'"']);
{$ENDIF}
AnAVLNode:=FItems.FindLowest;
cPriorityCount := 0;
while AnAVLNode<>nil do begin
CurItem:=TIdentifierListItem(AnAVLNode.Data);
if CurItem.Identifier<>'' then
begin
if FContainsFilter then
i:=IdentifierPos(PChar(Pointer(Prefix)),PChar(Pointer(CurItem.Identifier)))
else if ComparePrefixIdent(PChar(Pointer(Prefix)),PChar(Pointer(CurItem.Identifier))) then
i:=0
else
i:=-1;
if i=0 then begin
{$IFDEF ShowFilteredIdents}
DebugLn(['::: FILTERED ITEM ',FFilteredList.Count,' ',CurItem.Identifier]);
{$ENDIF}
if (length(Prefix)=length(CurItem.Identifier))
and (not (iliAtCursor in CurItem.Flags)) then
// put exact matches at the beginning
FFilteredList.Insert(0,CurItem)
else
FFilteredList.Insert(cPriorityCount, CurItem);
Inc(cPriorityCount);
end
else if i>0 then begin
{$IFDEF ShowFilteredIdents}
DebugLn(['::: FILTERED ITEM ',FFilteredList.Count,' ',CurItem.Identifier]);
{$ENDIF}
FFilteredList.Add(CurItem);
end;
end;
AnAVLNode:=FItems.FindSuccessor(AnAVLNode);
end;
if Assigned(FOnGatherUserIdentifiersToFilteredList) then
FOnGatherUserIdentifiersToFilteredList(Self, FFilteredList, cPriorityCount);
{$IFDEF CTDEBUG}
DebugLn(['TIdentifierList.UpdateFilteredList ',dbgs(FFilteredList.Count),' of ',dbgs(FItems.Count)]);
{$ENDIF}
Exclude(FFlags,ilfFilteredListNeedsUpdate);
end;
procedure TIdentifierList.SetHistory(const AValue: TIdentifierHistoryList);
begin
if FHistory=AValue then exit;
FHistory:=AValue;
end;
procedure TIdentifierList.SetSortForHistory(AValue: boolean);
begin
if FSortForHistory=AValue then Exit;
FSortForHistory:=AValue;
Clear;
end;
function TIdentifierList.GetFilteredItems(Index: integer): TIdentifierListItem;
begin
UpdateFilteredList;
if (Index<0) or (Index>=FFilteredList.Count) then
Result:=nil
else
Result:=TIdentifierListItem(FFilteredList[Index]);
end;
constructor TIdentifierList.Create;
begin
FFlags:=[ilfFilteredListNeedsUpdate];
FItems:=TAvlTree.CreateObjectCompare(@CompareIdentListItems);
FIdentView:=TAVLTree.Create(@CompareIdentListItemsForIdents);
FIdentSearchItem:=TIdentifierListSearchItem.Create;
FCreatedIdentifiers:=TFPList.Create;
FSortForHistory:=true;
FSortMethodForCompletion:=icsScopedAlphabetic;
end;
destructor TIdentifierList.Destroy;
begin
Clear;
FreeAndNil(FUsedTools);
FreeAndNil(FItems);
FreeAndNil(FIdentView);
FreeAndNil(FFilteredList);
FreeAndNil(FIdentSearchItem);
FreeAndNil(FCreatedIdentifiers);
inherited Destroy;
end;
procedure TIdentifierList.Clear;
var
i: Integer;
p: Pointer;
begin
fContextFlags:=[];
fContext:=CleanFindContext;
FNewMemberVisibility:=ctnNone;
FStartBracketLvl:=0;
fStartContext:=CleanFindContext;
fStartContextPos.Code:=nil;
fStartContextPos.X:=1;
fStartContextPos.Y:=1;
for i:=0 to FCreatedIdentifiers.Count-1 do begin
p:=FCreatedIdentifiers[i];
FreeMem(p);
end;
FCreatedIdentifiers.Clear;
FItems.FreeAndClear;
FIdentView.Clear;
if FUsedTools<>nil then
FUsedTools.Clear;
FFlags:=FFlags+[ilfFilteredListNeedsUpdate,ilfUsedToolsNeedsUpdate];
end;
procedure TIdentifierList.Add(NewItem: TIdentifierListItem);
var
AnAVLNode: TAVLTreeNode;
begin
if (ilcfDontAllowProcedures in ContextFlags) and (NewItem.GetDesc = ctnProcedure) and
not (NewItem.IsFunction or NewItem.IsConstructor)
then
begin
NewItem.Free;
Exit;
end;
AnAVLNode:=FIdentView.FindKey(NewItem,@CompareIdentListItemsForIdents);
if AnAVLNode=nil then begin
if History<>nil then
NewItem.HistoryIndex:=History.GetHistoryIndex(NewItem);
FItems.Add(NewItem);
FIdentView.Add(NewItem);
FFlags:=FFlags+[ilfFilteredListNeedsUpdate,ilfUsedToolsNeedsUpdate];
end else begin
// redefined identifier -> ignore
//DebugLn('TIdentifierList.Add redefined: ',NewItem.AsString);
NewItem.Free;
end;
end;
function TIdentifierList.Count: integer;
begin
Result:=FItems.Count;
end;
function TIdentifierList.GetFilteredCount: integer;
begin
UpdateFilteredList;
Result:=FFilteredList.Count;
end;
function TIdentifierList.HasIdentifier(Identifier: PChar;
const ParamList: string): boolean;
begin
FIdentSearchItem.Identifier:=Identifier;
FIdentSearchItem.ParamList:=ParamList;
Result:=FIdentView.FindKey(FIdentSearchItem,
@CompareIdentListSearchWithItems)<>nil;
end;
function TIdentifierList.FindIdentifier(Identifier: PChar;
const ParamList: string): TIdentifierListItem;
var
AVLNode: TAVLTreeNode;
begin
FIdentSearchItem.Identifier:=Identifier;
FIdentSearchItem.ParamList:=ParamList;
AVLNode:=FIdentView.FindKey(FIdentSearchItem,@CompareIdentListSearchWithItems);
if AVLNode<>nil then
Result:=TIdentifierListItem(AVLNode.Data)
else
Result:=nil;
end;
function TIdentifierList.FindIdentifier(Identifier: PChar; PreferProc: boolean
): TIdentifierListItem;
var
AVLNode: TAVLTreeNode;
StartNode: TAVLTreeNode;
begin
Result:=nil;
FIdentSearchItem.Identifier:=Identifier;
// ignore ParamList (for checking function overloading)
StartNode:=FIdentView.FindKey(FIdentSearchItem,@CompareIdentListSearchWithItemsWithoutParams);
if StartNode=nil then exit;
// identifier found, check preference
if (TIdentifierListItem(StartNode.Data).GetDesc in [ctnProcedure,ctnProcedureHead])=PreferProc
then
exit(TIdentifierListItem(StartNode.Data));
// identifier is a (not) proc, find the same identifier that fits PreferProc
// search in next nodes
AVLNode:=StartNode;
repeat
AVLNode:=FIdentView.FindSuccessor(AVLNode);
if (AVLNode=nil)
or (CompareIdentifiers(Identifier,PChar(TIdentifierListItem(AVLNode.Data).Identifier))<>0)
then break;
if (TIdentifierListItem(AVLNode.Data).GetDesc in [ctnProcedure,ctnProcedureHead])=PreferProc
then
exit(TIdentifierListItem(AVLNode.Data));
until false;
// search in previous nodes
AVLNode:=StartNode;
repeat
AVLNode:=FIdentView.FindPrecessor(AVLNode);
if (AVLNode=nil)
or (CompareIdentifiers(Identifier,PChar(TIdentifierListItem(AVLNode.Data).Identifier))<>0)
then break;
if (TIdentifierListItem(AVLNode.Data).GetDesc in [ctnProcedure,ctnProcedureHead])=PreferProc
then
exit(TIdentifierListItem(AVLNode.Data));
until false;
end;
function TIdentifierList.FindCreatedIdentifier(const Ident: string): integer;
begin
if Ident<>'' then begin
Result:=FCreatedIdentifiers.Count-1;
while (Result>=0)
and (CompareIdentifiers(PChar(Pointer(Ident)),
PChar(Pointer(FCreatedIdentifiers[Result])))<>0)
do
dec(Result);
end else begin
Result:=-1;
end;
end;
function TIdentifierList.FindIdentifier(Identifier: PChar): TIdentifierListItem;
var
Node: TAVLTreeNode;
begin
FIdentSearchItem.Identifier:=Identifier;
// ignore ParamList
Node:=FIdentView.FindKey(FIdentSearchItem,@CompareIdentListSearchWithItemsWithoutParams);
if Assigned(Node) then
Result := TIdentifierListItem(Node.Data)
else
Result := nil;
end;
function TIdentifierList.CreateIdentifier(const Ident: string): PChar;
var
i: Integer;
begin
if Ident<>'' then begin
i:=FindCreatedIdentifier(Ident);
if i>=0 then
Result:=PChar(Pointer(FCreatedIdentifiers[i]))
else begin
GetMem(Result,length(Ident)+1);
Move(Ident[1],Result^,length(Ident)+1);
FCreatedIdentifiers.Add(Result);
end;
end else
Result:=nil;
end;
function TIdentifierList.StartUpAtomInFrontIs(const s: string): boolean;
begin
Result:=StartContext.Tool.FreeUpAtomIs(StartAtomInFront,s);
end;
function TIdentifierList.StartUpAtomBehindIs(const s: string): boolean;
begin
Result:=StartContext.Tool.FreeUpAtomIs(StartAtomBehind,s);
end;
function TIdentifierList.CompletePrefix(const OldPrefix: string): string;
// search all identifiers beginning with Prefix
// and return the biggest shared prefix of all of them
var
AnAVLNode: TAvlTreeNode;
CurItem: TIdentifierListItem;
FoundFirst: Boolean;
SamePos: Integer;
l: Integer;
begin
Result:=OldPrefix;
FoundFirst:=false;
AnAVLNode:=FItems.FindLowest;
while AnAVLNode<>nil do begin
CurItem:=TIdentifierListItem(AnAVLNode.Data);
if (CurItem.Identifier<>'')
and ComparePrefixIdent(PChar(Pointer(Prefix)),PChar(Pointer(CurItem.Identifier)))
and (not (iliAtCursor in CurItem.Flags))
then begin
if not FoundFirst then begin
Result:=CurItem.Identifier;
FoundFirst:=true;
end else begin
SamePos:=length(Prefix)+1;
l:=length(Result);
if l>length(CurItem.Identifier) then
l:=length(CurItem.Identifier);
while (SamePos<=l)
and (UpChars[CurItem.Identifier[SamePos]]=UpChars[Result[SamePos]])
do
inc(SamePos);
if SamePos<=length(Result) then begin
Result:=copy(Result,1,SamePos-1);
if length(Result)=length(Prefix) then exit;
end;
end;
end;
AnAVLNode:=FItems.FindSuccessor(AnAVLNode);
end;
end;
function TIdentifierList.CalcMemSize: PtrUInt;
var
i: Integer;
Node: TAVLTreeNode;
AvgNode: TAvlTreeNode;
li: TIdentifierListItem;
hli: TIdentHistListItem;
begin
Result:=PtrUInt(InstanceSize)
+MemSizeString(FPrefix);
if FCreatedIdentifiers<>nil then begin
inc(Result,MemSizeFPList(FCreatedIdentifiers));
for i:=0 to FCreatedIdentifiers.Count-1 do
{%H-}inc(Result,GetIdentLen(PChar(FCreatedIdentifiers[i])));
end;
if FFilteredList<>nil then begin
inc(Result,MemSizeFPList(FFilteredList));
for i:=0 to FFilteredList.Count-1 do
inc(Result,TIdentifierListItem(FFilteredList[i]).CalcMemSize);
end;
if FHistory<>nil then begin
inc(Result,FHistory.CalcMemSize);
end;
if FItems<>nil then begin
{%H-}inc(Result,FItems.Count*SizeOf(TAvlTreeNode));
AvgNode:=FItems.FindLowest;
while AvgNode<>nil do begin
li:=TIdentifierListItem(AvgNode.Data);
inc(Result,li.CalcMemSize);
AvgNode:=AvgNode.Successor;
end;
end;
if FIdentView<>nil then begin
{%H-}inc(Result,FIdentView.Count*SizeOf(TAVLTreeNode));
Node:=FIdentView.FindLowest;
while Node<>nil do begin
hli:=TIdentHistListItem(Node.Data);
inc(Result,hli.CalcMemSize);
Node:=FIdentView.FindSuccessor(Node);
end;
end;
if FIdentSearchItem<>nil then
inc(Result,FIdentSearchItem.CalcMemSize);
end;
{ TIdentCompletionTool }
procedure TIdentCompletionTool.AddToTreeOfUnitFileInfo(const AFilename: string);
begin
AddToTreeOfUnitFilesOrNamespaces(FIDTTreeOfUnitFiles,FIDTTreeOfNamespaces,
FIDTTreeOfUnitFiles_NamespacePath,AFilename,FIDTTreeOfUnitFiles_CaseInsensitive,false);
end;
procedure TIdentCompletionTool.AddCompilerProcedure(const AProcName, AParameterList: PChar);
var
NewItem: TIdentifierListItem;
begin
//DebugLn(['AddCompilerProcedure ',AProcName,' ',ilcfStartOfStatement in CurrentIdentifierList.ContextFlags]);
if (ilcfDontAllowProcedures in CurrentIdentifierList.ContextFlags) then exit;
NewItem:=CIdentifierListItem.Create(
icompUnknown,
false,
CompilerFuncHistoryIndex,
AProcName,
CompilerFuncLevel,
nil,
nil,
ctnProcedure);
NewItem.ParamTypeList:=AParameterList;
NewItem.ParamNameList:=AParameterList;
NewItem.Flags:=NewItem.Flags+[iliParamTypeListValid,iliParamNameListValid];
CurrentIdentifierList.Add(NewItem);
end;
procedure TIdentCompletionTool.AddKeyWord(aKeyWord: string);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompExact,false,0,
CurrentIdentifierList.CreateIdentifier(aKeyWord),
1000,nil,nil,ctnNone);
include(NewItem.Flags,iliKeyword);
CurrentIdentifierList.Add(NewItem);
end;
procedure TIdentCompletionTool.AddCompilerFunction(const AProcName, AParameterList,
AResultType: PChar);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompUnknown,
false,
CompilerFuncHistoryIndex,
AProcName,
CompilerFuncLevel,
nil,
nil,
ctnProcedure);
NewItem.ParamTypeList:=AParameterList;
NewItem.ParamNameList:=AParameterList;
NewItem.ResultType:=AResultType;
NewItem.Flags:=NewItem.Flags+[iliParamTypeListValid,iliParamNameListValid,
iliIsFunction,iliIsFunctionValid,iliResultTypeValid];
CurrentIdentifierList.Add(NewItem);
end;
procedure TIdentCompletionTool.AddBaseType(const BaseName: PChar);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompUnknown,
false,
CompilerFuncHistoryIndex,
BaseName,
CompilerFuncLevel,
nil,
nil,
ctnTypeDefinition);
CurrentIdentifierList.Add(NewItem);
end;
procedure TIdentCompletionTool.AddBaseConstant(const BaseName: PChar);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompUnknown,
false,
CompilerFuncHistoryIndex,
BaseName,
CompilerFuncLevel,
nil,
nil,
ctnConstant);
CurrentIdentifierList.Add(NewItem);
end;
function TIdentCompletionTool.CollectAllIdentifiers(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
var
Ident: PChar;
CurContextParent: TCodeTreeNode;
function ProtectedNodeIsInAllowedClass: boolean;
var
CurClassNode: TCodeTreeNode;
FoundClassContext: TFindContext;
begin
Result:=false;
if (FICTClassAndAncestorsAndExtClassOfHelper<>nil) then begin
// start of the identifier completion is in a method or class
// => all protected ancestor classes are allowed as well.
CurClassNode:=FoundContext.Node;
while (CurClassNode<>nil)
and (not (CurClassNode.Desc in AllClasses)) do
CurClassNode:=CurClassNode.Parent;
if CurClassNode=nil then exit;
FoundClassContext:=CreateFindContext(Params.NewCodeTool,CurClassNode);
if IndexOfFindContext(FICTClassAndAncestorsAndExtClassOfHelper,@FoundClassContext)>=0 then begin
// this class node is the class or one of the ancestors of the class or extended class of the helper+ancestors
// of the start context of the identifier completion
exit(true);
end;
end;
//DebugLn(['ProtectedNodeIsInAllowedClass hidden: ',FindContextToString(FoundContext)]);
end;
function PropertyIsOverridenPublicPublish: boolean;
begin
// protected properties can be made public in child classes.
//debugln('PropertyIsOverridenPublicPublish Identifier=',GetIdentifier(Ident),' Find=',dbgs((FIDCTFoundPublicProperties<>nil) and (FIDCTFoundPublicProperties.Find(Ident)<>nil)));
if FIDCTFoundPublicProperties<>nil then begin
if FIDCTFoundPublicProperties.Find(Ident)<>nil then begin
// there is a public/published property with the same name
exit(true);
end;
end;
Result:=false;
end;
procedure SavePublicPublishedProperty;
begin
if FIDCTFoundPublicProperties=nil then begin
// create tree
FIDCTFoundPublicProperties:=
TAVLTree.Create(TListSortCompare(@CompareIdentifiers))
end else if FIDCTFoundPublicProperties.Find(Ident)<>nil then begin
// identifier is already public
exit;
end;
FIDCTFoundPublicProperties.Add(Ident);
//debugln('SavePublicPublishedProperty Identifier=',GetIdentifier(Ident),' Find=',dbgs(FIDCTFoundPublicProperties.Find(Ident)<>nil));
end;
var
NewItem: TIdentifierListItem;
Node: TCodeTreeNode;
ProtectedForeignClass: Boolean;
Lvl: LongInt;
NamePos: TAtomPosition;
HasLowerVisibility: Boolean;
begin
// proceed searching ...
Result:=ifrProceedSearch;
{$IFDEF ShowFoundIdents}
if FoundContext.Tool=Self then
DebugLn('::: COLLECT IDENT ',FoundContext.Node.DescAsString,
' "',StringToPascalConst(copy(FoundContext.Tool.Src,FoundContext.Node.StartPos,50)),'"'
,' '+dbgs(fdfIgnoreUsedUnits in Params.Flags));
{$ENDIF}
CurContextParent:=FoundContext.Node.GetFindContextParent;
if FLastGatheredIdentParent<>CurContextParent then begin
// new context level
FLastGatheredIdentParent:=CurContextParent;
inc(FLastGatheredIdentLevel);
end;
Lvl:=FLastGatheredIdentLevel;
HasLowerVisibility:=False;
ProtectedForeignClass:=false;
if FoundContext.Tool=Self then begin
// identifier is in the same unit
//DebugLn('::: COLLECT IDENT in SELF ',FoundContext.Node.DescAsString,
// ' "',dbgstr(FoundContext.Tool.Src,FoundContext.Node.StartPos,50),'"'
// ,' fdfIgnoreUsedUnits='+dbgs(fdfIgnoreUsedUnits in Params.Flags));
if (FoundContext.Node=CurrentIdentifierList.StartContext.Node)
or (FoundContext.Node=CurrentIdentifierList.Context.Node)
or (FoundContext.Node.StartPos=CurrentIdentifierList.StartAtom.StartPos)
then begin
// found identifier is in cursor node
// => do not show it
exit;
end;
end else begin
// identifier is in another unit
Node:=FoundContext.Node.Parent;
if (Node<>nil) and (Node.Desc in AllClassSubSections) then
Node:=Node.Parent;
if (Node<>nil) and (Node.Desc in AllClassBaseSections) then begin
//debugln(['TIdentCompletionTool.CollectAllIdentifiers Node=',Node.DescAsString,' Context=',CurrentIdentifierList.Context.Node.DescAsString,' CtxVis=',NodeDescToStr(CurrentIdentifierList.NewMemberVisibility)]);
if (CurrentIdentifierList.NewMemberVisibility<>ctnNone)
and (CurrentIdentifierList.NewMemberVisibility<Node.Desc)
and (FoundContext.Node.Desc
in ([ctnProcedure,ctnProcedureHead,ctnProperty]+AllClassSections))
then begin
// the user wants to override a method or property
// => ignore all with a higher visibility, because fpc does not allow
// to downgrade the visibility and will give a hint when trying
//---- No, allow visibility downgrading to reduce confusion tha CodeTools do not list those functions.
//---- FPC actually allows it although it shows a warning
//debugln(['TIdentCompletionTool.CollectAllIdentifiers skipping member, because it would downgrade: ',dbgstr(FoundContext.Tool.ExtractNode(FoundContext.Node,[]),1,30)]);
HasLowerVisibility:=True;
end;
case Node.Desc of
ctnClassPrivate:
begin
// skip private definitions in other units
exit;
end;
ctnClassProtected:
begin
// protected definitions are only accessible from descendants
// or if visibility was raised (e.g. property)
if ProtectedNodeIsInAllowedClass then begin
// protected node in an ancestor => allowed
//debugln('TIdentCompletionTool.CollectAllIdentifiers ALLOWED Protected in ANCESTOR '+StringToPascalConst(copy(FoundContext.Tool.Src,FoundContext.Node.StartPos,50)));
end else if (FoundContext.Node.Desc=ctnProperty) then begin
// protected property: maybe the visibility was raised => continue
ProtectedForeignClass:=true;
//debugln('TIdentCompletionTool.CollectAllIdentifiers MAYBE Protected made Public '+StringToPascalConst(copy(FoundContext.Tool.Src,FoundContext.Node.StartPos,50)));
end else begin
// otherwise: treat as private
//debugln('TIdentCompletionTool.CollectAllIdentifiers FORBIDDEN Protected '+StringToPascalConst(copy(FoundContext.Tool.Src,FoundContext.Node.StartPos,50)));
exit;
end;
end;
end;
end;
end;
Ident:=nil;
case FoundContext.Node.Desc of
ctnTypeDefinition,ctnGenericType:
begin
Node:=FoundContext.Node.FirstChild;
if FoundContext.Node.Desc=ctnTypeDefinition then
Ident:=@FoundContext.Tool.Src[FoundContext.Node.StartPos]
else begin
// generic
if Node=nil then exit;
Ident:=@FoundContext.Tool.Src[Node.StartPos];
end;
if Node=nil then begin
// type without definition
end;
if (Node<>nil)
and (Node.Desc in AllClasses)
and ((ctnsForwardDeclaration and Node.SubDesc)>0)
then begin
// forward definition of a class
if CurrentIdentifierList.FindIdentifier(Ident,'')<>nil then begin
// the real class is already in the list => skip forward
exit;
end;
end;
end;
ctnGenericParameter:
Ident:=@FoundContext.Tool.Src[FoundContext.Node.StartPos];
ctnVarDefinition,ctnConstDefinition,ctnEnumIdentifier,ctnLabel,ctnGlobalProperty:
Ident:=@FoundContext.Tool.Src[FoundContext.Node.StartPos];
ctnProcedure,ctnProcedureHead:
//do not list class constructors and destructors
if not FoundContext.Tool.NodeIsClassConstructorOrDestructor(FoundContext.Node) then
begin
Ident:=FoundContext.Tool.GetProcNameIdentifier(FoundContext.Node);
NewItem := CurrentIdentifierList.FindIdentifier(Ident,true);
if (NewItem<>nil) and (NewItem.Tool<>nil) then begin
if (NewItem.GetNode<>nil) then begin
if (FoundContext.Node.Parent.Desc in AllClassBaseSections)
<> (NewItem.Node.Parent.Desc in AllClassBaseSections)
then
exit; // class members hide normal procs and nested procs hide class members
if (Lvl <> NewItem.Level) then begin
// there is a previous declaration on a different level
if (NewItem.Node.Desc<>ctnProcedure)
or (not NewItem.Tool.ProcNodeHasSpecifier(NewItem.Node, psOVERLOAD))
then
exit; // there is a previous declaration without 'overload'
end;
end;
end;
end;
ctnProperty:
begin
Ident:=FoundContext.Tool.GetPropertyNameIdentifier(FoundContext.Node);
if FoundContext.Tool.PropNodeIsTypeLess(FoundContext.Node) then begin
if FoundContext.Node.Parent.Desc in [ctnClassPublic,ctnClassPublished]
then
SavePublicPublishedProperty;
// do not show properties without types (e.g. property Color;)
// only show the real definition, which will follow in the ancestor
exit;
end;
if (FoundContext.Node.Parent.Desc=ctnClassPrivate)
and (FoundContext.Tool<>Self)
and (not PropertyIsOverridenPublicPublish) then begin
// a private property in another unit, that was not
// made public/publish later
// => skip
exit;
end;
if (FoundContext.Node.Parent.Desc=ctnClassProtected)
and ProtectedForeignClass
and (not PropertyIsOverridenPublicPublish) then begin
// a protected property in another unit, that was not
// made public/publish later
// => skip
exit;
end;
end;
ctnRecordCase:
Ident:=@FoundContext.Tool.Src[Params.NewCleanPos];
ctnUseUnitNamespace,ctnUseUnitClearName:
if (FoundContext.Tool=Self) then begin
Ident:=@Src[FoundContext.Node.StartPos];
end;
ctnUnit,ctnProgram,ctnLibrary,ctnPackage:
if (FoundContext.Tool=Self)
and GetSourceNamePos(NamePos) then
Ident:=@Src[NamePos.StartPos];
end;
if Ident=nil then exit;
NewItem:=CIdentifierListItem.Create(
icompUnknown,
false,
0,
Ident,
Lvl,
FoundContext.Node,
FoundContext.Tool,
ctnNone);
//Add the '&' character to prefixed identifiers
if (Ident^='&') and (IsIdentStartChar[Ident[1]]) then
Include(NewItem.Flags,iliNeedsAmpersand);
// found identifier is in cursor node
if (FoundContext.Node=CurrentIdentifierList.StartContext.Node) then
Include(NewItem.Flags,iliAtCursor);
// method has lower visibility
if HasLowerVisibility then
Include(NewItem.Flags,iliHasLowerVisibility);
{$IFDEF ShowFoundIdents}
if FoundContext.Tool=Self then
DebugLn(' IDENT COLLECTED: ',NewItem.AsString);
{$ENDIF}
CurrentIdentifierList.Add(NewItem);
end;
procedure TIdentCompletionTool.GatherPredefinedIdentifiers(CleanPos: integer;
const Context, GatherContext: TFindContext);
// Add predefined identifiers
function StatementLevel: integer;
var
ANode: TCodeTreeNode;
begin
Result:=0;
ANode:=Context.Node;
while (ANode<>nil) and (not (ANode.Desc in [ctnBeginBlock,ctnAsmBlock])) do
begin
ANode:=ANode.Parent;
inc(Result);
end;
if ANode=nil then Result:=0;
end;
procedure AddSystemUnit(const AnUnitName: PChar);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CUnitNameSpaceIdentifierListItem.Create(
icompUnknown,
false,
CompilerFuncHistoryIndex,
AnUnitName,
CompilerFuncLevel,
nil,
nil,
ctnUseUnitClearName,
AnUnitName,
1);
CurrentIdentifierList.Add(NewItem);
end;
var
NewItem: TIdentifierListItem;
ProcNode: TCodeTreeNode;
HiddenUnits: String;
p: PChar;
SystemTool: TFindDeclarationTool;
I: TExpressionTypeDesc;
InSystemContext: Boolean;
FPCFulVersion: LongInt;
begin
if CleanPos=0 then ;
SystemTool := FindCodeToolForUsedUnit('System','',False);
InSystemContext :=
(ilcfStartOfOperand in CurrentIdentifierList.ContextFlags) or
((ilcfStartIsSubIdent in CurrentIdentifierList.ContextFlags) and
(GatherContext.Tool<>nil) and (GatherContext.Node<>nil) and (SystemTool<>nil) and
(GatherContext.Tool = SystemTool) and (GatherContext.Node = SystemTool.FindInterfaceNode));
if InSystemContext and (Context.Node.Desc in AllPascalStatements) then
begin
// see fpc/compiler/psystem.pp
FPCFulVersion:=StrToIntDef(Scanner.Values['FPC_FULLVERSION'],0);
AddCompilerProcedure('Assert','Condition:Boolean;const Message:String');
AddCompilerFunction('Assigned','P:Pointer','Boolean');
AddCompilerFunction('Addr','var X','Pointer');
AddCompilerFunction('BitSizeOf','Identifier','Integer');
AddCompilerProcedure('Break','');
AddCompilerFunction('Concat','S1:String;S2:String[...;Sn:String]', 'String');
if FPCFulVersion>=30100 then
AddCompilerFunction('Concat','A1:Array;[...;An:Array]', 'Array');
AddCompilerProcedure('Continue','');
if FPCFulVersion>=30100 then
begin
// FromPosition and Count parameters are optional
AddCompilerFunction('Copy','const S:string[;FromPosition,Count:SizeInt]', 'string');
AddCompilerFunction('Copy','const A:array[;FromPosition,Count:SizeInt]', 'string');
end else
begin
AddCompilerFunction('Copy','const S:string;FromPosition,Count:SizeInt', 'string');
AddCompilerFunction('Copy','const A:array;FromPosition,Count:SizeInt', 'string');
end;
AddCompilerProcedure('Dec','var X:Ordinal;N:Integer=1');
AddCompilerFunction('Default','T:Type','const');
if FPCFulVersion>=30100 then //Delete and Insert are available as intrinsic since FPC 3.1
begin
AddCompilerProcedure('Delete','var S:string;Index,Count:Integer');
AddCompilerProcedure('Delete','var A:array;Index,Count:Integer');
AddCompilerProcedure('Insert','const Source:string;var Dest:string;Index:Integer');
AddCompilerProcedure('Insert','Item; var A:array;Index:Integer');
end;
AddCompilerProcedure('Dispose','var X:Pointer');
AddCompilerProcedure('Exclude','var S:Set;X:Ordinal');
AddCompilerProcedure('Exit','');
AddCompilerProcedure('Finalize','var X');
AddCompilerFunction('get_frame','','Pointer');
AddCompilerFunction('High','Arg:TypeOrVariable','Ordinal');
AddCompilerProcedure('Inc','var X:Ordinal;N:Integer=1');
AddCompilerProcedure('Include','var S:Set;X:Ordinal');
AddCompilerProcedure('Initialize','var X');
AddCompilerFunction('Length','S:String','SizeInt');
AddCompilerFunction('Length','A:Array','SizeInt');
AddCompilerFunction('Low','Arg:TypeOrVariable','Ordinal');
AddCompilerProcedure('New','var X:Pointer');
AddCompilerFunction('ObjCSelector','String','SEL');
AddCompilerFunction('Ofs','var X','LongInt');
AddCompilerFunction('Ord','X:Ordinal', 'Integer');
AddCompilerProcedure('Pack','A:Array;N:Integer;var A:Array');
AddCompilerFunction('Pred','X:Ordinal', 'Ordinal');
AddCompilerProcedure('Read','');
AddCompilerProcedure('ReadLn','');
AddCompilerProcedure('ReadStr','S:String;var Args:Arguments');
AddCompilerFunction('Seg','var X','LongInt');
AddCompilerProcedure('SetLength','var S:String;NewLength:SizeInt');
AddCompilerProcedure('SetLength','var A:Array;NewLength:SizeInt');
if Scanner.Values.IsDefined('FPC_HAS_CPSTRING') then begin
AddCompilerProcedure('SetString','out S:RawByteString;Buf:PAnsiChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:AnsiString;Buf:PAnsiChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:AnsiString;Buf:PWideChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:ShortString;Buf:PChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:UnicodeString;Buf:PUnicodeChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:UnicodeString;Buf:PChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:WideString;Buf:PWideChar;Len:SizeInt');
AddCompilerProcedure('SetString','out S:WideString;Buf:PChar;Len:SizeInt');
end;
AddCompilerFunction('SizeOf','Identifier','Integer');
AddCompilerFunction('Slice','var A:Array;Count:Integer','Array');
AddCompilerProcedure('Str','const X[:Width[:Decimals]];var S:String');
AddCompilerFunction('Succ','X:Ordinal', 'Ordinal');
AddCompilerFunction('TypeInfo','Identifier', 'Pointer');
AddCompilerFunction('GetTypeKind','Identifier', 'TTypeKind');
AddCompilerFunction('IsManagedType','Identifier', 'Boolean');
AddCompilerFunction('IsConstValue','const Value', 'Boolean');
AddCompilerFunction('TypeOf','Identifier', 'Pointer');
AddCompilerProcedure('Val','S:String;var V;var Code:Integer');
AddCompilerFunction('Unaligned','var X','var'); // Florian declaration :)
AddCompilerProcedure('Unpack','A:Array;var A:Array;N:Integer');
AddCompilerProcedure('Write','Args:Arguments');
AddCompilerProcedure('WriteLn','Args:Arguments');
AddCompilerProcedure('WriteStr','var S:String;Args:Arguments');
if Scanner.PascalCompiler=pcPas2js then begin
AddCompilerFunction('Str','const X[:Width[:Decimals]]','string');
AddCompilerFunction('AWait','const Expr: T','T');
AddCompilerFunction('AWait','aType; p: TJSPromise','aType');
end;
end;
if (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags) and
(Context.Node.Desc in AllPascalStatements)
then
begin
if (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags)
and Context.Tool.NodeIsInAMethod(Context.Node)
and (not CurrentIdentifierList.HasIdentifier('Self','')) then begin
// method body -> add 'Self'
NewItem:=CIdentifierListItem.Create(
icompUnknown,
true,
1,
'Self',
StatementLevel,
nil,
nil,
ctnVarDefinition);
CurrentIdentifierList.Add(NewItem);
end;
ProcNode:=Context.Node.GetNodeOfType(ctnProcedure);
if (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags)
and Context.Tool.NodeIsFunction(ProcNode)
and (not CurrentIdentifierList.HasIdentifier('Result','')) then begin
// function body -> add 'Result'
NewItem:=CIdentifierListItem.Create(
icompUnknown,
true,
1,
'Result',
StatementLevel,
nil,
nil,
ctnVarDefinition);
CurrentIdentifierList.Add(NewItem);
end;
end;
// system types
if InSystemContext then
begin
for I in [xtChar..xtPointer, xtLongint..xtByte, xtVariant] do
AddBaseType(PChar(ExpressionTypeDescNames[I]));
if not (ilcfStartInStatement in CurrentIdentifierList.ContextFlags) then
for I in [xtFile, xtText] do
AddBaseType(PChar(ExpressionTypeDescNames[I]));
if Scanner.PascalCompiler=pcPas2js then begin
for I in xtAllPas2JSExtraTypes do
AddBaseType(PChar(ExpressionTypeDescNames[I]));
end;
AddBaseConstant('True');
AddBaseConstant('False');
//the nil constant doesn't belong to system context, therefore it is added in next step
end;
if (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags) then
begin
AddBaseConstant(PChar(ExpressionTypeDescNames[xtNil]));
// system units
HiddenUnits:=Scanner.GetHiddenUsedUnits;
if HiddenUnits<>'' then begin
p:=PChar(HiddenUnits);
while p^<>#0 do begin
while p^=',' do inc(p);
if GetIdentLen(p)>0 then
AddSystemUnit(p);
while not (p^ in [',',#0]) do inc(p);
end;
end;
end;
end;
procedure TIdentCompletionTool.GatherUsefulIdentifiers(CleanPos: integer;
const Context, GatherContext: TFindContext);
procedure AddPropertyProc(ProcName: string);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompExact,true,0,
CurrentIdentifierList.CreateIdentifier(ProcName),
0,nil,nil,ctnProcedure);
CurrentIdentifierList.Add(NewItem);
end;
var
PropertyName: String;
begin
//debugln(['TIdentCompletionTool.GatherUsefulIdentifiers ',CleanPosToStr(CleanPos),' ',dbgsFC(Context)]);
GatherPredefinedIdentifiers(CleanPos,Context,GatherContext);
if Context.Node.Desc=ctnProperty then begin
PropertyName:=ExtractPropName(Context.Node,false);
//debugln('TIdentCompletionTool.GatherUsefulIdentifiers Property ',PropertyName);
MoveCursorToCleanPos(CleanPos);
ReadPriorAtom;
//debugln(['TIdentCompletionTool.GatherUsefulIdentifiers Atom=',GetAtom]);
if UpAtomIs('READ') then begin
// add the default class completion 'read' specifier function
AddPropertyProc(Beautifier.PropertyReadIdentPrefix+PropertyName);
end;
if UpAtomIs('WRITE') then begin
// add the default class completion 'write' specifier function
AddPropertyProc(Beautifier.PropertyWriteIdentPrefix+PropertyName);
end;
if (UpAtomIs('READ') or UpAtomIs('WRITE'))
and (Context.Tool.FindClassOrInterfaceNode(Context.Node)<>nil)
then begin
// add the default class completion 'read'/'write' specifier variable
AddPropertyProc(Beautifier.PrivateVariablePrefix+PropertyName);
end;
if UpAtomIs('STORED') then begin
// add the default class completion 'stored' specifier function
AddPropertyProc(PropertyName+Beautifier.PropertyStoredIdentPostfix);
end;
end;
end;
procedure TIdentCompletionTool.GatherUserIdentifiers(
const ContextFlags: TIdentifierListContextFlags);
begin
if Assigned(FOnGatherUserIdentifiers) then
FOnGatherUserIdentifiers(Self, ContextFlags);
end;
procedure TIdentCompletionTool.GatherUnitnames(const NameSpacePath: string);
procedure GatherUnitsFromSet;
begin
// collect all unit files in fpc unit paths
DirectoryCache.IterateFPCUnitsInSet(@AddToTreeOfUnitFileInfo);
end;
var
UnitPath, SrcPath: string;
BaseDir: String;
ANode: TAVLTreeNode;
UnitFileInfo: TUnitFileInfo;
NewItem: TUnitNameSpaceIdentifierListItem;
UnitExt: String;
SrcExt: String;
CurSourceName: String;
NameSpaceInfo: TNameSpaceInfo;
begin
UnitPath:='';
SrcPath:='';
GatherUnitAndSrcPath(UnitPath,SrcPath);
CurSourceName:=GetSourceName;
//DebugLn('TIdentCompletionTool.GatherUnitnames CurSourceName="',CurSourceName,'" UnitPath="',UnitPath,'" SrcPath="',SrcPath,'"');
BaseDir:=ExtractFilePath(MainFilename);
FIDTTreeOfUnitFiles:=nil;
FIDTTreeOfNamespaces:=nil;
try
// search in unitpath
FIDTTreeOfUnitFiles_CaseInsensitive := true;
FIDTTreeOfUnitFiles_NamespacePath := NameSpacePath;
{$IFDEF VerboseICGatherUnitNames}
FIDTTreeOfUnitFiles:=TAVLTree.Create(@CompareUnitFileInfos);
{$ENDIF}
UnitExt:=PascalCompilerUnitExt[Scanner.PascalCompiler];
if Scanner.CompilerMode=cmMacPas then
UnitExt:=UnitExt+';p';
GatherUnitFiles(BaseDir,UnitPath,UnitExt,NameSpacePath,false,true,FIDTTreeOfUnitFiles, FIDTTreeOfNamespaces);
{$IFDEF VerboseICGatherUnitNames}
debugln(['TIdentCompletionTool.GatherUnitnames UnitPath ',FIDTTreeOfUnitFiles.Count]);
{$ENDIF}
// search in srcpath
SrcExt:=PascalCompilerSrcExt[Scanner.PascalCompiler];
if Scanner.CompilerMode=cmMacPas then
SrcExt:=SrcExt+';p';
GatherUnitFiles(BaseDir,SrcPath,SrcExt,NameSpacePath,false,true,FIDTTreeOfUnitFiles, FIDTTreeOfNamespaces);
{$IFDEF VerboseICGatherUnitNames}
debugln(['TIdentCompletionTool.GatherUnitnames Plus SrcPath ',FIDTTreeOfUnitFiles.Count]);
{$ENDIF}
// add default units
GatherUnitsFromSet;
{$IFDEF VerboseICGatherUnitNames}
debugln(['TIdentCompletionTool.GatherUnitnames Plus FPC units ',FIDTTreeOfUnitFiles.Count]);
{$ENDIF}
// create list
if FIDTTreeOfUnitFiles<>nil then
begin
ANode:=FIDTTreeOfUnitFiles.FindLowest;
while ANode<>nil do begin
UnitFileInfo:=TUnitFileInfo(ANode.Data);
ANode:=FIDTTreeOfUnitFiles.FindSuccessor(ANode);
if CompareText(PChar(Pointer(UnitFileInfo.FileUnitName)), Length(UnitFileInfo.FileUnitName),
PChar(Pointer(CurSourceName)), Length(CurSourceName), False)=0
then
continue;
NewItem:=CUnitNameSpaceIdentifierListItem.Create(
icompCompatible,true,0,
CurrentIdentifierList.CreateIdentifier(UnitFileInfo.FileUnitNameWithoutNamespace),
0,nil,nil,ctnUnit, PChar(UnitFileInfo.FileUnitName), UnitFileInfo.IdentifierStartInUnitName);
if NewItem.IdentifierStartInUnitName < 1 then
NewItem.IdentifierStartInUnitName := 1;
{$IFDEF VerboseICGatherUnitNames}
//debugln(['TIdentCompletionTool.GatherUnitnames Add ',UnitFileInfo.FileUnitName,' NewCount=',CurrentIdentifierList]);
{$ENDIF}
CurrentIdentifierList.Add(NewItem);
end;
end;
if FIDTTreeOfNamespaces<>nil then
begin
ANode:=FIDTTreeOfNamespaces.FindLowest;
while ANode<>nil do begin
NameSpaceInfo:=TNameSpaceInfo(ANode.Data);
NewItem:=CUnitNameSpaceIdentifierListItem.Create(
icompCompatible,true,0,
CurrentIdentifierList.CreateIdentifier(NameSpaceInfo.NameSpace),
0,nil,nil,ctnUseUnitNamespace, PChar(NameSpaceInfo.UnitName),
NameSpaceInfo.IdentifierStartInUnitName);
CurrentIdentifierList.Add(NewItem);
ANode:=FIDTTreeOfNamespaces.FindSuccessor(ANode);
end;
end;
finally
FreeTreeOfUnitFiles(FIDTTreeOfUnitFiles);
FreeTreeOfUnitFiles(FIDTTreeOfNamespaces);
end;
end;
procedure TIdentCompletionTool.GatherSourceNames(const Context: TFindContext);
procedure Add(const SrcName: string);
var
NewItem: TIdentifierListItem;
begin
NewItem:=CIdentifierListItem.Create(
icompExact,true,0,
CurrentIdentifierList.CreateIdentifier(SrcName),
0,nil,nil,Context.Node.Desc);
CurrentIdentifierList.Add(NewItem);
end;
var
NewSourceName: String;
FileSourceName: String;
begin
// add the unitname as in the filename and as in the source
FileSourceName:=ExtractFilenameOnly(MainFilename);
NewSourceName:=GetSourceName(false);
//DebugLn('TIdentCompletionTool.GatherSourceNames FileSourceName=',FileSourceName,' NewSourceName=',NewSourceName);
if (FileSourceName<>lowercase(FileSourceName)) then begin
// the file is not written lowercase => case is important, ignore source name
Add(FileSourceName);
end else if (SysUtils.CompareText(NewSourceName,FileSourceName)<>0) then begin
// source name is not correct => only use file name
Add(FileSourceName);
end else if NewSourceName=FileSourceName then begin
// both are the same => add only one
Add(FileSourceName);
end else begin
// both are valid, just different in case
// the filename is written lowercase
// => prefer the source name
Add(NewSourceName);
end;
end;
procedure TIdentCompletionTool.GatherContextKeywords(
const Context: TFindContext; CleanPos: integer;
BeautifyCodeOptions: TBeautifyCodeOptions; const GatherContext: TFindContext);
type
TPropertySpecifier = (
psIndex,psRead,psWrite,psStored,psImplements,psDefault,psNoDefault
);
TPropertySpecifiers = set of TPropertySpecifier;
procedure Add(Keyword: string);
var
NewItem: TIdentifierListItem;
begin
KeyWord:=BeautifyCodeOptions.BeautifyKeyWord(Keyword);
NewItem:=CIdentifierListItem.Create(
icompExact,false,0,
CurrentIdentifierList.CreateIdentifier(Keyword),
1000,nil,nil,ctnNone);
include(NewItem.Flags,iliKeyword);
CurrentIdentifierList.Add(NewItem);
end;
procedure AddSpecifiers(Forbidden: TPropertySpecifiers);
begin
if not (psIndex in Forbidden) then Add('index');
if not (psRead in Forbidden) then Add('read');
if not (psWrite in Forbidden) then Add('write');
if not (psStored in Forbidden) then Add('stored');
if not (psImplements in Forbidden) then Add('implements');
if not (psDefault in Forbidden) then Add('default');
if not (psNoDefault in Forbidden) then Add('nodefault');
end;
procedure CheckProperty(PropNode: TCodeTreeNode);
var
Forbidden: TPropertySpecifiers;
begin
if not MoveCursorToPropType(PropNode) then exit;
if CleanPos<CurPos.EndPos then exit;
ReadNextAtom;
if CurPos.Flag=cafPoint then begin
ReadNextAtom;
if CurPos.Flag<>cafWord then exit;
ReadNextAtom;
end;
Forbidden:=[];
repeat
if CleanPos<=CurPos.EndPos then begin
AddSpecifiers(Forbidden);
exit;
end;
if (not (psIndex in Forbidden)) and UpAtomIs('INDEX') then begin
ReadNextAtom;
Include(Forbidden,psIndex);
end else if (not (psRead in Forbidden)) and UpAtomIs('READ') then begin
ReadNextAtom;
Forbidden:=Forbidden+[psIndex..psRead];
end else if (not (psWrite in Forbidden)) and UpAtomIs('WRITE') then begin
ReadNextAtom;
Forbidden:=Forbidden+[psIndex..psWrite];
end else if (not (psImplements in Forbidden)) and UpAtomIs('IMPLEMENTS')
then begin
ReadNextAtom;
exit;
end else if (not (psStored in Forbidden)) and UpAtomIs('STORED') then
begin
ReadNextAtom;
Forbidden:=Forbidden+[psIndex..psImplements];
end else if (not (psDefault in Forbidden)) and UpAtomIs('DEFAULT') then
begin
ReadNextAtom;
exit;
end else if (not (psNoDefault in Forbidden)) and UpAtomIs('NODEFAULT') then
begin
ReadNextAtom;
exit;
end else if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
if not ReadTilBracketClose(false) then exit;
end else
ReadNextAtom;
until (CleanPos<CurPos.StartPos) or (CurPos.EndPos>SrcLen);
end;
procedure AddMethodSpecifiers;
var
i: Integer;
begin
for i:=0 to IsKeyWordMethodSpecifier.Count-1 do
Add(IsKeyWordMethodSpecifier.GetItem(i).KeyWord+';');
end;
procedure AddProcSpecifiers;
var
i: Integer;
begin
for i:=0 to IsKeyWordProcedureSpecifier.Count-1 do
Add(IsKeyWordProcedureSpecifier.GetItem(i).KeyWord+';');
end;
procedure AddProcTypeSpecifiers;
var
i: Integer;
begin
for i:=0 to IsKeyWordProcedureTypeSpecifier.Count-1 do
Add(IsKeyWordProcedureTypeSpecifier.GetItem(i).KeyWord+';');
end;
var
Node, SubNode, NodeInFront: TCodeTreeNode;
p, AtomStartPos, AtomEndPos: Integer;
NodeBehind, LastChild: TCodeTreeNode;
NotStartOfOp: Boolean;
begin
try
AtomStartPos:=CleanPos;
AtomEndPos:=CleanPos;
NodeInFront:=nil;
Node:=Context.Node;
if Node<>nil then begin
MoveCursorToNearestAtom(CleanPos);
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords MoveCursorToNearestAtom Node=',Node.DescAsString,' Atom="',GetAtom,'"']);
{$ENDIF}
ReadNextAtom;
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords MoveCursorToNearestAtom+ReadNextAtom Node=',Node.DescAsString,' Atom="',GetAtom,'"']);
{$ENDIF}
AtomStartPos:=CurPos.StartPos;
AtomEndPos:=CurPos.EndPos;
if CleanPos<=AtomEndPos then begin
// CleanPos is within an atom
while (Node.Parent<>nil)
and (AtomStartPos=Node.StartPos) do
// at the start of the node -> the node is created by the atom at cursor
// use parent as context
Node:=Node.Parent;
// get node in front
ReadPriorAtomSafe(AtomStartPos);
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords prioratom=',CleanPosToStr(CurPos.StartPos),'="',GetAtom(CurPos),'"']);
{$ENDIF}
if CurPos.StartPos>0 then
NodeInFront:=FindDeepestNodeAtPos(CurPos.StartPos,false);
end else begin
// CleanPos is between an atom
NodeInFront:=FindDeepestNodeAtPos(AtomEndPos,false);
end;
end;
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords Node=',Node.DescAsString,' Atom="',GetAtom,'"']);
{$ENDIF}
NodeBehind:=nil;
MoveCursorToCleanPos(AtomStartPos);
ReadNextAtom;
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords nextatom=',CleanPosToStr(CurPos.StartPos),'=',GetAtom(CurPos)]);
{$ENDIF}
if CurPos.StartPos>CleanPos then
NodeBehind:=FindDeepestNodeAtPos(CurPos.StartPos,false);
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords CASE Node=',Node.DescAsString,' NodeInFront=',NodeInFront.DescAsString,' NodeBehind=',NodeBehind.DescAsString]);
{$ENDIF}
case Node.Desc of
ctnClass,ctnObject,ctnRecordType,ctnObjCCategory,ctnObjCClass,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnClassPrivate,ctnClassProtected,ctnClassPublic,ctnClassPublished:
begin
Add('public');
Add('private');
Add('protected');
Add('published');
Add('procedure');
Add('function');
Add('property');
if (Node.Desc=ctnClass) or (Node.Parent.Desc=ctnClass) then begin
Add('constructor');
Add('destructor');
end;
if (Node.Desc=ctnRecordType) or (Node.Parent.Desc=ctnRecordType) then begin
Add('case');
end;
LastChild:=Node.LastChild;
if (LastChild<>nil) and (CleanPos>LastChild.StartPos)
and (LastChild.EndPos>LastChild.StartPos)
and (LastChild.EndPos<Srclen) then begin
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords end of class section ',dbgstr(copy(Src,Node.LastChild.EndPos-10,10))]);
{$ENDIF}
SubNode:=LastChild;
if SubNode.Desc=ctnProperty then begin
CheckProperty(SubNode);
end;
end;
end;
ctnClassInterface,ctnDispinterface,ctnObjCProtocol,ctnCPPClass:
begin
Add('procedure');
Add('function');
end;
ctnInterface,ctnImplementation:
begin
if (Node.FirstChild=nil)
or ((Node.FirstChild.Desc<>ctnUsesSection)
and (Node.FirstChild.StartPos>=CleanPos))
then
Add('uses');
Add('type');
Add('var');
Add('const');
Add('label');
Add('procedure');
Add('function');
Add('resourcestring');
if Node.Desc=ctnInterface then begin
Add('property');
end;
if (NodeBehind=nil)
or (NodeBehind.Desc in [ctnInitialization,ctnFinalization,ctnEndPoint,ctnBeginBlock])
then begin
if Node.Desc=ctnInterface then
Add('implementation');
Add('initialization');
Add('finalization');
end;
end;
ctnInitialization:
if (NodeBehind=nil)
or (NodeBehind.Desc in [ctnInitialization,ctnFinalization,ctnEndPoint,ctnBeginBlock])
then begin
Add('finalization');
Add('begin');
end;
ctnProcedure:
begin
Add('begin');
Add('type');
Add('var');
Add('const');
Add('label');
Add('procedure');
Add('function');
if not (ilcfDontAllowProcedures in CurrentIdentifierList.ContextFlags)
and (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags)
then begin
Add('asm');
end;
end;
ctnProcedureHead:
begin
MoveCursorBehindProcName(Node);
p:=CurPos.StartPos;
while (p>=1) and (Src[p] in [' ',#9]) do dec(p);
if CleanPos>=p then
AddMethodSpecifiers;
end;
ctnVarDefinition:
if Node.Parent.Desc in [ctnClass,ctnObject,ctnRecordType,ctnObjCCategory,ctnObjCClass]
+AllClassBaseSections
then begin
Add('public');
Add('private');
Add('protected');
Add('published');
Add('procedure');
Add('function');
Add('property');
if [cmsObjectiveC1,cmsObjectiveC2]*Scanner.CompilerModeSwitches<>[] then
begin
Add('required');
Add('optional');
end;
if (Node.Desc=ctnClass) or (Node.Parent.Desc=ctnClass) then begin
Add('constructor');
Add('destructor');
end;
if (Node.Desc=ctnRecordType) or (Node.Parent.Desc=ctnRecordType) then begin
Add('case');
end;
end
else
if Node.Parent.Desc = ctnOnBlock then
begin
Add('do');
end;
ctnTypeSection,ctnVarSection,ctnConstSection,ctnLabelSection,ctnResStrSection,
ctnLibrary,ctnProgram:
begin
Add('type');
Add('const');
Add('var');
Add('resourcestring');
Add('label');
Add('procedure');
Add('function');
Add('property');
if Node.Desc=ctnLibrary then begin
Add('initialization');
Add('finalization');
Add('begin');
end;
end;
ctnWithVariable, ctnOnBlock:
begin
Add('do');
end;
ctnBeginBlock,ctnWithStatement,ctnOnStatement:
//ctnInitialization,ctnFinalization: //AllPascalStatements
begin
if CurrentIdentifierList.IdentComplIncludeKeywords and
(GatherContext.Node <> nil) and
(GatherContext.Node.Desc in [ctnBeginBlock,ctnWithStatement,ctnOnStatement])
then
begin
if not (ilcfDontAllowProcedures in CurrentIdentifierList.ContextFlags)
and (ilcfStartOfStatement in CurrentIdentifierList.ContextFlags)
then begin
Add('asm');
Add('begin');
Add('case');
Add('except');
Add('finally');
Add('for');
Add('goto');
Add('if');
Add('raise');
Add('repeat');
Add('try');
Add('until');
Add('while');
Add('with');
end;
if (ilcfStartInStatement in CurrentIdentifierList.ContextFlags)
and not (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags)
and (CurrentIdentifierList.StartBracketLvl = 0)
then begin
Add('else');
Add('end');
Add('then');
Add('do');
Add('downto');
Add('of');
Add('on');
Add('to');
end;
if (ilcfStartOfStatement in CurrentIdentifierList.ContextFlags)
or (CurrentIdentifierList.ContextFlags * [ilcfStartInStatement, ilcfStartOfOperand] = [ilcfStartInStatement, ilcfStartOfOperand])
then
begin
Add('inherited');
end;
if (CurrentIdentifierList.ContextFlags * [ilcfIsExpression, ilcfStartInStatement] <> []) then
begin
NotStartOfOp := not (ilcfStartOfOperand in CurrentIdentifierList.ContextFlags);
if not NotStartOfOp then begin
MoveCursorToAtomPos(CurrentIdentifierList.StartAtomInFront);
NotStartOfOp := AtomIsNumber or AtomIsRealNumber;
end;
if NotStartOfOp then
begin
Add('and');
Add('or');
Add('xor');
Add('div');
Add('in');
Add('as');
Add('is');
Add('mod');
Add('shl');
Add('shr');
end;
Add('not');
end;
end;
end;
ctnProperty:
CheckProperty(Node);
end;
if NodeInFront<>nil then begin
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords Check NodeInFront=',NodeInFront.DescAsString]);
{$ENDIF}
SubNode:=NodeInFront;
while (SubNode<>nil) and (SubNode.EndPos<=CleanPos) do begin
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords Check NodeInFront SubNode=',SubNode.DescAsString]);
{$ENDIF}
if (SubNode.Desc=ctnProcedureHead) then begin
// e.g. in interface: procedure DoIt; v|
// procedure head postfix modifiers
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords SubNode.Parent=',SubNode.Parent.DescAsString]);
{$ENDIF}
if SubNode.Parent.Desc=ctnProcedure then begin
{$IFDEF VerboseICGatherKeywords}
debugln(['TIdentCompletionTool.GatherContextKeywords SubNode.Parent.Parent=',SubNode.Parent.Parent.DescAsString]);
{$ENDIF}
if SubNode.Parent.Parent.Desc in (AllClasses+AllClassBaseSections) then
AddMethodSpecifiers
else
AddProcSpecifiers;
end else if SubNode.Parent.Desc=ctnProcedureType then begin
AddProcTypeSpecifiers;
end;
break;
end;
SubNode:=SubNode.Parent;
{$IFDEF VerboseICGatherKeywords}
if (SubNode<>nil) and (SubNode.EndPos>CleanPos) then
debugln(['TIdentCompletionTool.GatherContextKeywords EndOfCheck NodeInFront SubNode=',SubNode.DescAsString]);
{$ENDIF}
end;
end;
except
// ignore parser errors
on E: ECodeToolError do ;
on E: ELinkScannerError do ;
end;
end;
procedure TIdentCompletionTool.InitCollectIdentifiers(
const CursorPos: TCodeXYPosition; var IdentifierList: TIdentifierList);
var
StartContext: TFindContext;
begin
if IdentifierList=nil then IdentifierList:=TIdentifierList.Create;
CurrentIdentifierList:=IdentifierList;
CurrentIdentifierList.Clear;
FLastGatheredIdentParent:=nil;
FLastGatheredIdentLevel:=0;
CurrentIdentifierList.StartContextPos:=CursorPos;
StartContext := CurrentIdentifierList.StartContext;
StartContext.Tool := Self;
CurrentIdentifierList.StartContext:=StartContext;
end;
function TIdentCompletionTool.ParseSourceTillCollectionStart(
const CursorPos: TCodeXYPosition; out CleanCursorPos: integer;
out CursorNode: TCodeTreeNode; out IdentStartPos, IdentEndPos: integer): boolean;
var
StartContext: TFindContext;
ContextPos: Integer;
begin
Result:=false;
CleanCursorPos:=0;
CursorNode:=nil;
IdentStartPos:=0;
IdentEndPos:=0;
// build code tree
{$IFDEF CTDEBUG}
DebugLn(['TIdentCompletionTool.ParseSourceTillCollectionStart A CursorPos=',dbgs(CursorPos)]);
{$ENDIF}
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
// Return if CleanCursorPos is before Tree.Root.StartNode.
// For example a comment at the beginning of a unit.
if (Tree.Root=nil) or (Tree.Root.StartPos>CleanCursorPos) then
Exit;
if FindDeepestNodeAtPos(CleanCursorPos,false)=nil then
begin
debugln(['TIdentCompletionTool.ParseSourceTillCollectionStart',
' BuildTreeAndGetCleanPos worked, but no node found.',
' CursorPos=',dbgs(CursorPos),' CleanCursorPos=',CleanCursorPos,
' ScannedRange=',dbgs(ScannedRange),
' Scanner.ScannedRange=',dbgs(Scanner.ScannedRange),
' IgnoreErrorAfterValid=',IgnoreErrorAfterValid
]);
if IgnoreErrorAfterValid then
debugln([' IgnoreErrorAfter=',dbgs(IgnoreErrorAfter),' IgnoreErrorAfterCleanedPos=',IgnoreErrorAfterCleanedPos,' CleanPosIsAfterIgnorePos=',CleanPosIsAfterIgnorePos(CleanCursorPos)]);
if CursorPos.Y<=CursorPos.Code.LineCount then
debugln([' Line=',dbgstr(CursorPos.Code.GetLine(CursorPos.Y-1,true),1,CursorPos.X-1),'|',dbgstr(CursorPos.Code.GetLine(CursorPos.Y-1,true),CursorPos.X,100)]);
CursorNode:=Tree.Root;
if CursorNode=nil then begin
debugln([' no nodes']);
end else begin
while CursorNode.NextBrother<>nil do
CursorNode:=CursorNode.NextBrother;
while CursorNode<>nil do begin
debugln([' Node=',CursorNode.DescAsString,',Start=',CursorNode.StartPos,',End=',CursorNode.EndPos,',Src="...',dbgstr(RightStr(ExtractNode(CursorNode,[]),100)),'"']);
CursorNode:=CursorNode.LastChild;
end;
end;
end;
// find node at position
ContextPos:=CleanCursorPos;
// The context node might be in front of the CleanCursorPos
// For example: A.|end; In this case the statement ends at the point.
// Check the atom in front
ReadPriorAtomSafe(CleanCursorPos);
if (CurPos.Flag<>cafNone) then begin
if (CurPos.Flag in [cafPoint,cafRoundBracketOpen,cafEdgedBracketOpen])
or UpAtomIs('INHERITED') then
ContextPos:=CurPos.StartPos;
end;
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(ContextPos,true);
if CurrentIdentifierList<>nil then begin
StartContext:=CurrentIdentifierList.StartContext;
StartContext.Node:=CursorNode;
CurrentIdentifierList.StartContext:=StartContext;
end;
// get identifier position
if CursorPos.Code.LineColIsOutside(CursorPos.Y,CursorPos.X) then begin
IdentStartPos:=CleanCursorPos;
IdentEndPos:=CleanCursorPos;
end else begin
GetIdentStartEndAtPosition(Src,CleanCursorPos,IdentStartPos,IdentEndPos);
end;
//DebugLn(['TIdentCompletionTool.ParseSourceTillCollectionStart ',dbgstr(copy(Src,IdentStartPos,10)),' CursorPos.X=',CursorPos.X,' LineLen=',CursorPos.Code.GetLineLength(CursorPos.Y-1),' ',CursorPos.Code.GetLine(CursorPos.Y-1)]);
if CursorPos.X>CursorPos.Code.GetLineLength(CursorPos.Y-1)+1 then
IdentStartPos:=IdentEndPos;
Result:=true;
end;
function TIdentCompletionTool.FindIdentifierStartPos(
const CursorPos: TCodeXYPosition): TCodeXYPosition;
var
p: integer;
IdentStartPos, IdentEndPos: integer;
begin
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,p);
if p<1 then
RaiseException(20170421201041,ctsCursorPosOutsideOfCode);
if CursorPos.X<=CursorPos.Code.GetLineLength(CursorPos.Y-1)+1 then begin
GetIdentStartEndAtPosition(CursorPos.Code.Source,p,IdentStartPos,IdentEndPos);
end else begin
IdentStartPos:=p;
IdentEndPos:=p;
end;
Result:=CursorPos;
if IdentStartPos>0 then
dec(Result.X,p-IdentStartPos);
//DebugLn(['TIdentCompletionTool.FindIdentifierStartPos ',dbgstr(copy(CursorPos.Code.Source,IdentStartPos,20))]);
end;
procedure TIdentCompletionTool.FindCollectionContext(
Params: TFindDeclarationParams; IdentStartPos: integer;
CursorNode: TCodeTreeNode; out ExprType: TExpressionType; out
ContextExprStartPos: LongInt; out StartInSubContext,
HasInheritedKeyword: Boolean);
function GetContextExprStartPos(IdentStartPos: integer;
ContextNode: TCodeTreeNode): integer;
begin
MoveCursorToCleanPos(IdentStartPos);
ReadPriorAtom;
HasInheritedKeyword := UpAtomIs('INHERITED');
if (CurPos.Flag=cafPoint)
or HasInheritedKeyword then begin
Result:=FindStartOfTerm(IdentStartPos,NodeTermInType(ContextNode));
if Result<ContextNode.StartPos then
Result:=ContextNode.StartPos;
end else
Result:=IdentStartPos;
MoveCursorToCleanPos(Result);
ReadNextAtom;
case ContextNode.Desc of
ctnProperty:
// check for special property keywords
if WordIsPropertySpecifier.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)
then
// do not resolve property specifiers
Result:=IdentStartPos;
end;
end;
var
IgnoreCurContext: Boolean;
GatherContext: TFindContext;
begin
GatherContext:=CreateFindContext(Self,CursorNode);
ExprType := CleanExpressionType;
IgnoreCurContext:=false;
//DebugLn(['TIdentCompletionTool.FindCollectionContext IdentStartPos=',dbgstr(copy(Src,IdentStartPos,20)),' ',CursorNode.DescAsString]);
ContextExprStartPos:=GetContextExprStartPos(IdentStartPos,CursorNode);
if GatherContext.Node.Desc=ctnWithVariable then begin
if GatherContext.Node.PriorBrother<>nil then
GatherContext.Node:=GatherContext.Node.PriorBrother
else
GatherContext.Node:=GatherContext.Node.Parent;
end
else if (GatherContext.Node.GetNodeOfType(ctnClassInheritance)<>nil) then
begin
while not (GatherContext.Node.Desc in AllClasses) do
GatherContext.Node:=GatherContext.Node.Parent;
GatherContext.Node:=GatherContext.Node.Parent;
IgnoreCurContext:=true;
end else if GatherContext.Node.Desc=ctnIdentifier then begin
IgnoreCurContext:=true;
end;
StartInSubContext:=false;
//DebugLn(['TIdentCompletionTool.FindCollectionContext ContextExprStartPos=',ContextExprStartPos,' "',dbgstr(copy(Src,ContextExprStartPos,20)),'" IdentStartPos="',dbgstr(copy(Src,IdentStartPos,20)),'" Gather=',FindContextToString(GatherContext)]);
if ContextExprStartPos<IdentStartPos then begin
MoveCursorToCleanPos(IdentStartPos);
Params.ContextNode:=CursorNode;
Params.SetIdentifier(Self,nil,nil);
Params.Flags:=[fdfExceptionOnNotFound,
fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,fdfTypeType];
if IgnoreCurContext then
Params.Flags:=Params.Flags+[fdfIgnoreCurContextNode];
ExprType:=FindExpressionTypeOfTerm(ContextExprStartPos,IdentStartPos,
Params,false);
if ExprType.Desc=xtContext then begin
GatherContext:=ExprType.Context;
//debugln(['TIdentCompletionTool.FindCollectionContext ',ExprTypeToString(ExprType)]);
StartInSubContext:=true;
end else begin
// for example "string.|"
GatherContext:=CleanFindContext;
end;
end;
ExprType.Context := GatherContext;
end;
function TIdentCompletionTool.CollectAllContexts(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
begin
Result:=ifrProceedSearch;
if FoundContext.Node=nil then exit;
//DebugLn(['TIdentCompletionTool.CollectAllContexts ',FoundContext.Node.DescAsString]);
case FoundContext.Node.Desc of
ctnProcedure:
begin
//DebugLn('TIdentCompletionTool.CollectAllContexts Found Proc CurrentContexts.ProcNameAtom.StartPos=',dbgs(CurrentIdentifierContexts.ProcNameAtom.StartPos));
if (CurrentIdentifierContexts.ProcName='') then exit;
FoundContext.Tool.MoveCursorToProcName(FoundContext.Node,true);
//DebugLn(['TIdentCompletionTool.CollectAllContexts ProcName=',GetIdentifier(@FoundContext.Tool.Src[FoundContext.Tool.CurPos.StartPos])]);
if FoundContext.Tool.CompareSrcIdentifiers(
FoundContext.Tool.CurPos.StartPos,
PChar(CurrentIdentifierContexts.ProcName))
then begin
// method without 'overload' hides inherited one
if not FoundContext.Tool.ProcNodeHasSpecifier(FoundContext.Node, psOVERLOAD) then
Exclude(Params.Flags, fdfSearchInAncestors);
end else exit;
end;
ctnProperty,ctnGlobalProperty:
begin
if (CurrentIdentifierContexts.ProcName='') then exit;
FoundContext.Tool.MoveCursorToPropName(FoundContext.Node);
if not FoundContext.Tool.CompareSrcIdentifiers(
FoundContext.Tool.CurPos.StartPos,
PChar(CurrentIdentifierContexts.ProcName))
then exit;
end;
ctnVarDefinition:
begin
//debugln(['TIdentCompletionTool.CollectAllContexts ',FoundContext.Tool.ExtractNode(FoundContext.Node,[])]);
if (CurrentIdentifierContexts.ProcName='') then exit;
if not FoundContext.Tool.CompareSrcIdentifiers(
FoundContext.Node.StartPos,
PChar(CurrentIdentifierContexts.ProcName))
then exit;
end;
else
exit;
end;
{$IFDEF VerboseCodeContext}
DebugLn(['TIdentCompletionTool.CollectAllContexts add ',FoundContext.Node.DescAsString]);
{$ENDIF}
AddCollectionContext(FoundContext.Tool,FoundContext.Node);
end;
function TIdentCompletionTool.CollectAttributeConstructors(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
begin
Result:=ifrProceedSearch;
if FoundContext.Node=nil then exit;
{$IFDEF VerboseCodeContext}
//DebugLn(['TIdentCompletionTool.CollectAttributeConstructors ',FoundContext.Node.DescAsString]);
{$ENDIF}
case FoundContext.Node.Desc of
ctnProcedure:
begin
{$IFDEF VerboseCodeContext}
//DebugLn('TIdentCompletionTool.CollectAttributeConstructors Found Proc ',FoundContext.Tool.ExtractProcName(FoundContext.Node,[]),' ',FoundContext.Tool.CleanPosToStr(FoundContext.Node.StartPos,true));
{$ENDIF}
if (CurrentIdentifierContexts.ProcName='') then exit;
if FoundContext.Tool.NodeIsConstructor(FoundContext.Node) then begin
{$IFDEF VerboseCodeContext}
DebugLn('TIdentCompletionTool.CollectAttributeConstructors Found Constructor ',FoundContext.Tool.ExtractProcName(FoundContext.Node,[]),' ',FoundContext.Tool.CleanPosToStr(FoundContext.Node.StartPos,true));
{$ENDIF}
AddCollectionContext(FoundContext.Tool,FoundContext.Node);
end;
// ToDo: method without 'overload' hides inherited one
//if not FoundContext.Tool.ProcNodeHasSpecifier(FoundContext.Node, psOVERLOAD) then
// Exclude(Params.Flags, fdfSearchInAncestors);
end;
else
exit;
end;
end;
procedure TIdentCompletionTool.AddCollectionContext(Tool: TFindDeclarationTool;
Node: TCodeTreeNode);
begin
if CurrentIdentifierContexts=nil then
CurrentIdentifierContexts:=TCodeContextInfo.Create;
CurrentIdentifierContexts.Add(CreateExpressionType(xtContext,xtNone,
CreateFindContext(Tool,Node)));
//DebugLn('TIdentCompletionTool.AddCollectionContext ',Node.DescAsString,' ',ExtractNode(Node,[]));
end;
function TIdentCompletionTool.CheckCursorInCompilerDirective(CursorPos: TCodeXYPosition
): boolean;
var
Line: String;
p: Integer;
EndPos: Integer;
InnerStart: Integer;
Directive: String;
ms: TCompilerModeSwitch;
cm: TCompilerMode;
OptimizerSwitch: TOptimizerSwitch;
SrcType: TCodeTreeNodeDesc;
Compiler: TPascalCompiler;
begin
Result:=false;
Line:=CursorPos.Code.GetLine(CursorPos.Y-1,false);
p:=1;
while p<=length(Line) do begin
p:=FindNextCompilerDirective(Line,p,Scanner.NestedComments);
if p>length(Line) then exit;
EndPos:=FindCommentEnd(Line,p,Scanner.NestedComments);
if (CursorPos.X>p) and (CursorPos.X<EndPos) then begin
// in a directive
Result:=true;
InnerStart:=p;
if Line[InnerStart]='{' then
inc(InnerStart,2)
else
inc(InnerStart,3);
//debugln(['TIdentCompletionTool.IsInCompilerDirective InnerStart=',InnerStart,' X=',CursorPos.X]);
SrcType:=GetSourceType;
Compiler:=Scanner.PascalCompiler;
if (InnerStart=CursorPos.X)
or ((CursorPos.X>=InnerStart) and (InnerStart<=length(Line))
and (CursorPos.X<=InnerStart+GetIdentLen(@Line[InnerStart])))
then begin
// at start of directive
// see fpc/compiler/scandir.pas (incomplete list, e.g. Define is missing there)
AddKeyWord('A1');
AddKeyWord('A2');
AddKeyWord('A4');
AddKeyWord('A8');
AddKeyWord('Align');
AddKeyWord('AlignAssertions');
AddKeyWord('AppID');
AddKeyWord('AppName');
AddKeyWord('AppType');
AddKeyWord('AsmMode');
AddKeyWord('Assertions');
AddKeyWord('BitPacking');
AddKeyWord('BoolEval');
AddKeyWord('Calling');
AddKeyWord('CheckLowAddrLoads');
AddKeyWord('CheckPointer');
AddKeyWord('CodeAlign');
AddKeyWord('Codepage');
AddKeyWord('COperators');
AddKeyWord('Copyright');
AddKeyWord('D');
AddKeyWord('DebugInfo');
AddKeyWord('Define');
if Compiler=pcDelphi then
AddKeyWord('DefinitionInfo');
if Compiler=pcDelphi then
AddKeyWord('DenyPackageUnit');
if (Compiler=pcDelphi) and (SrcType=ctnPackage) then
AddKeyWord('DesignOnly');
AddKeyWord('Description');
if (Compiler=pcPas2js) then begin
AddKeyWord('DispatchField');
AddKeyWord('DispatchStrField');
end;
AddKeyWord('ElIfC');
AddKeyWord('Else');
AddKeyWord('ElseC');
AddKeyWord('ElseIf');
AddKeyWord('EndC');
AddKeyWord('EndIf');
AddKeyWord('EndRegion');
AddKeyWord('Error');
AddKeyWord('ErrorC');
AddKeyWord('ExtendedSyntax');
if (Compiler=pcDelphi) and (SrcType in [ctnProgram,ctnLibrary,ctnPackage]) then
AddKeyWord('Extension');
AddKeyWord('ExternalSym');
AddKeyWord('F');
AddKeyWord('Fatal');
AddKeyWord('FPUType');
AddKeyWord('FrameworkPath');
AddKeyWord('Goto');
if Compiler=pcDelphi then
AddKeyWord('HighCharUnicode');
AddKeyWord('Hint');
AddKeyWord('Hints');
AddKeyWord('HPPEmit');
AddKeyWord('HugeCode');
AddKeyWord('HugePointerArithmetikNormalization');
AddKeyWord('HugePointerComparisonNormalization');
AddKeyWord('HugePointerNormalization');
AddKeyWord('IEEEErrors');
AddKeyWord('IfC');
AddKeyWord('IfDef');
AddKeyWord('IfEnd');
AddKeyWord('IfNDef');
AddKeyWord('IfOpt');
AddKeyWord('ImageBase');
if Compiler=pcDelphi then
AddKeyWord('ImplicitBuild');
AddKeyWord('ImplicitExceptions');
if Compiler=pcDelphi then
AddKeyWord('ImportedData');
AddKeyWord('Include');
AddKeyWord('IncludePath');
AddKeyWord('Info');
AddKeyWord('Inline');
AddKeyWord('Interfaces');
AddKeyWord('IOChecks');
AddKeyWord('L');
if Compiler=pcDelphi then
AddKeyWord('LegacyIfEnd');
AddKeyWord('LibExport');
if Compiler=pcDelphi then
AddKeyWord('LibPrefix');
if Compiler=pcDelphi then
AddKeyWord('LibPostfix');
AddKeyWord('LibraryPath');
if Compiler=pcDelphi then
AddKeyWord('LibVersion');
AddKeyWord('Link');
AddKeyWord('LinkFramework');
AddKeyWord('LinkLib');
AddKeyWord('LocalSymbols');
AddKeyWord('LongStrings');
AddKeyWord('M');
AddKeyWord('Macro');
AddKeyWord('MaxFPURegisters');
AddKeyWord('MaxStackSize');
AddKeyWord('Memory');
AddKeyWord('Message');
if Compiler=pcDelphi then
AddKeyWord('MethodInfo');
AddKeyWord('MinEnumSize');
AddKeyWord('MinFPConstPrec');
AddKeyWord('MMX');
AddKeyWord('Mode');
AddKeyWord('ModeSwitch');
AddKeyWord('NameSpace');
if Compiler=pcDelphi then
AddKeyWord('NoInclude');
AddKeyWord('Note');
AddKeyWord('Notes');
AddKeyWord('ObjectChecks');
if Compiler=pcDelphi then
AddKeyWord('ObjExportAll');
AddKeyWord('ObjectPath');
if Compiler=pcDelphi then
AddKeyWord('ObjTypeName');
if Compiler=pcDelphi then
AddKeyWord('OldTypeLayout');
AddKeyWord('OpenStrings');
AddKeyWord('Optimization');
AddKeyWord('Output_Format');
AddKeyWord('OV');
AddKeyWord('OverflowChecks');
AddKeyWord('PackEnum');
AddKeyWord('PackRecords');
AddKeyWord('PackSet');
AddKeyWord('PIC');
AddKeyWord('PointerMath');
AddKeyWord('Pop');
AddKeyWord('Profile');
AddKeyWord('Push');
AddKeyWord('R');
AddKeyWord('RangeChecks');
if Compiler=pcDelphi then
AddKeyWord('RealCompatibility');
AddKeyWord('ReferenceInfo');
AddKeyWord('Region');
AddKeyWord('Resource');
AddKeyWord('RTTI');
if (Compiler=pcDelphi) and (SrcType=ctnPackage) then
AddKeyWord('RunOnly');
if Compiler=pcDelphi then
AddKeyWord('SafeDivide');
AddKeyWord('SafeFPUExceptions');
AddKeyWord('Saturation');
AddKeyWord('ScopedEnums');
AddKeyWord('ScreenName');
AddKeyWord('SetC');
AddKeyWord('SetPEFlags');
AddKeyWord('SetPEOptFlags');
AddKeyWord('SetPEOSVersion');
AddKeyWord('SetPESubSysVersion');
AddKeyWord('SetPEUserVersion');
AddKeyWord('SmartLink');
AddKeyWord('StackFrames');
AddKeyWord('Stop');
AddKeyWord('StringChecks');
if Compiler=pcDelphi then
AddKeyWord('StrongLinkTypes');
AddKeyWord('Syscall');
AddKeyWord('TargetSwitch');
AddKeyWord('ThreadName');
AddKeyWord('TypedAddress');
AddKeyWord('TypeInfo');
AddKeyWord('GetTypeKind');
AddKeyWord('UnDef');
AddKeyWord('UnitPath');
AddKeyWord('VarParaCopyOutCheck');
AddKeyWord('VarPropSetter');
AddKeyWord('VarStringChecks');
AddKeyWord('Wait');
AddKeyWord('Warn');
AddKeyWord('Warning');
AddKeyWord('Warnings');
AddKeyWord('WeakPackageUnit');
AddKeyWord('WriteableConst'); // unusual spelling in fpc
if Compiler=pcDelphi then
AddKeyWord('ExtendedCompatibility');
if Compiler=pcDelphi then
AddKeyWord('ExtendedSyntax');
if Compiler=pcDelphi then
AddKeyWord('ExternalSym');
if Compiler=pcDelphi then
AddKeyWord('ExcessPrecision');
AddKeyWord('Z1');
AddKeyWord('Z2');
AddKeyWord('Z4');
AddKeyWord('ZeroBasedStrings');
end else if InnerStart<=length(Line) then begin
// in parameter of directive
Directive:=lowercase(GetIdentifier(@Line[InnerStart]));
if (Directive='ifdef')
or (Directive='ifndef')
or (Directive='if')
or (Directive='elseif')
or (Directive='ifc')
then begin
AddCompilerDirectiveMacros(Directive);
end else if Directive='modeswitch' then begin
for ms:=low(TCompilerModeSwitch) to high(TCompilerModeSwitch) do
AddKeyWord(lowercase(CompilerModeSwitchNames[ms]));
end else if Directive='mode' then begin
for cm:=low(TCompilerMode) to high(TCompilerMode) do
AddKeyWord(lowercase(CompilerModeNames[cm]));
end else if Directive='warn' then begin
AddKeyWord('constructing_abstract');
AddKeyWord('implicit_variants');
AddKeyWord('no_retval');
AddKeyWord('symbol_deprecated');
AddKeyWord('symbol_experimental');
AddKeyWord('symbol_library');
AddKeyWord('symbol_platform');
AddKeyWord('symbol_unimplemented');
AddKeyWord('unit_deprecated');
AddKeyWord('unit_experimental');
AddKeyWord('unit_library');
AddKeyWord('unit_platform');
AddKeyWord('unit_unimplemented');
AddKeyWord('zero_nil_compat');
AddKeyWord('implicit_string_cast');
AddKeyWord('implicit_variants');
AddKeyWord('no_retval');
AddKeyWord('symbol_deprecated');
AddKeyWord('symbol_experimental');
AddKeyWord('symbol_library');
AddKeyWord('symbol_platform');
AddKeyWord('symbol_unimplemented');
AddKeyWord('unit_deprecated');
AddKeyWord('unit_experimental');
AddKeyWord('unit_library');
AddKeyWord('unit_platform');
AddKeyWord('unit_unimplemented');
AddKeyWord('zero_nil_compat');
AddKeyWord('implicit_string_cast');
AddKeyWord('implicit_string_cast_loss');
AddKeyWord('explicit_string_cast');
AddKeyWord('explicit_string_cast_loss');
AddKeyWord('cvt_narrowing_string_lost');
end else if (Directive='i') or (Directive='include') then begin
AddKeyWord('Date');
AddKeyWord('FPCTarget');
AddKeyWord('FPCTargetOS');
AddKeyWord('FPCTargetCPU');
AddKeyWord('FPCVersion');
AddKeyWord('Time');
AddKeyWord('CurrentRoutine'); // since FPC 3.1+
AddKeyWord('Line'); // since FPC 3.1+
end else if (Directive='codepage') then begin
// see fpcsrc/compiler/widestr.pas
AddKeyWord('UTF8');
AddKeyWord('cp1250');
AddKeyWord('cp1251');
AddKeyWord('cp1252');
AddKeyWord('cp1253');
AddKeyWord('cp1254');
AddKeyWord('cp1255');
AddKeyWord('cp1256');
AddKeyWord('cp1257');
AddKeyWord('cp1258');
AddKeyWord('cp437');
AddKeyWord('cp646');
AddKeyWord('cp850');
AddKeyWord('cp852');
AddKeyWord('cp856');
AddKeyWord('cp866');
AddKeyWord('cp874');
AddKeyWord('cp8859_1');
AddKeyWord('cp8859_2');
AddKeyWord('cp8859_5');
end else if Directive='interfaces' then begin
AddKeyWord('COM');
AddKeyWord('CORBA');
end else if Directive='optimization' then begin
for OptimizerSwitch in TOptimizerSwitch do
AddKeyWord(OptimizerSwitchStr[OptimizerSwitch]);
end;
end;
exit;
end;
p:=EndPos;
end;
end;
procedure TIdentCompletionTool.AddCompilerDirectiveMacros(Directive: string);
var
Macros: TStringToStringTree;
StrItem: PStringToStringItem;
CodeBufs: TAVLTree;
AVLNode: TAVLTreeNode;
procedure Add(e: TExpressionEvaluator);
var
i: Integer;
begin
for i:=0 to e.Count-1 do
Macros[e.Names(i)]:=e.Values(i);
end;
procedure AddExprWords(CodeBuf: TCodeBuffer);
var
CurSrc: String;
p: Integer;
sp: PChar;
NamePos: PChar;
EndP: PChar;
CurName: String;
begin
p:=1;
CurSrc:=CodeBuf.Source;
while p<=length(CurSrc) do begin
p:=FindNextCompilerDirective(CurSrc,p,Scanner.NestedComments);
if p>length(CurSrc) then break;
sp:=@CurSrc[p];
p:=FindCommentEnd(CurSrc,p,Scanner.NestedComments);
// skip comment start
if sp^='{' then inc(sp,2)
else if sp^='(' then inc(sp,3);
if not IsIdentStartChar[sp^] then break;
NamePos:=sp;
inc(sp,GetIdentLen(NamePos));
if sp^=#0 then break;
if (CompareIdentifiers(NamePos,'ifdef')=0)
or (CompareIdentifiers(NamePos,'ifndef')=0)
or (CompareIdentifiers(NamePos,'if')=0)
or (CompareIdentifiers(NamePos,'ifc')=0)
or (CompareIdentifiers(NamePos,'elseif')=0)
or (CompareIdentifiers(NamePos,'elifc')=0)
or (CompareIdentifiers(NamePos,'define')=0)
or (CompareIdentifiers(NamePos,'unde')=0)
or (CompareIdentifiers(NamePos,'setc')=0)
then begin
// add all identifiers in directive
if p>length(CurSrc) then
EndP:=PChar(CurSrc)+length(CurSrc)
else
EndP:=@CurSrc[p];
while (sp<EndP) do begin
if IsIdentStartChar[sp^] then begin
CurName:=GetIdentifier(sp);
if (CompareIdentifiers(sp,'defined')<>0)
and (CompareIdentifiers(sp,'undefined')<>0) then begin
if not Macros.Contains(CurName) then begin
Macros[CurName]:='';
end;
end;
inc(sp,length(CurName));
end else begin
inc(sp);
end;
end;
end;
end;
end;
begin
CodeBufs:=nil;
Macros:=TStringToStringTree.Create(false);
try
Add(Scanner.InitialValues);
Add(Scanner.Values);
if (Directive='if') or (Directive='elseif')
or (Directive='ifc') or (Directive='elifc') then begin
AddCompilerFunction('defined','','boolean');
AddCompilerFunction('undefined','','boolean');
end;
// add all words of all directives in unit
CodeBufs:=Scanner.CreateTreeOfSourceCodes;
AVLNode:=CodeBufs.FindLowest;
while AVLNode<>nil do begin
AddExprWords(TCodeBuffer(AVLNode.Data));
AVLNode:=CodeBufs.FindSuccessor(AVLNode);
end;
for StrItem in Macros do
AddKeyWord(StrItem^.Name);
finally
CodeBufs.Free;
Macros.Free;
end;
end;
function TIdentCompletionTool.GatherAvailableUnitNames(const CursorPos: TCodeXYPosition;
var IdentifierList: TIdentifierList): Boolean;
begin
Result:=false;
try
InitCollectIdentifiers(CursorPos, IdentifierList);
GatherUnitNames;
Result:=true;
finally
CurrentIdentifierList:=nil;
end;
end;
function TIdentCompletionTool.GatherIdentifiers(
const CursorPos: TCodeXYPosition; var IdentifierList: TIdentifierList
): boolean;
var
CleanCursorPos, IdentStartPos, IdentEndPos: integer;
CursorNode: TCodeTreeNode;
Params: TFindDeclarationParams;
GatherContext: TFindContext;
ContextExprStartPos: Integer;
StartInSubContext: Boolean;
StartPosOfVariable: LongInt;
CursorContext: TFindContext;
IdentStartXY: TCodeXYPosition;
InFrontOfDirective, HasInheritedKeyword: Boolean;
ExprType: TExpressionType;
IdentifierPath: string;
procedure CheckProcedureDeclarationContext;
var
Node: TCodeTreeNode;
Can: Boolean;
begin
//DebugLn(['CheckProcedureDeclarationContext ',CursorNode.DescAsString]);
Node:=CursorNode;
Can:=false;
if (Node.Parent<>nil)
and (Node.Parent.Desc in AllClassSections)
and (Node.Desc=ctnVarDefinition)
and (CurrentIdentifierList.StartAtomBehind.Flag<>cafColon) then begin
{ cursor is at a class variable definition without type
for example:
public
MouseM|
end;
}
Can:=true;
end
else if (((Node.Desc=ctnProcedure) and (not NodeIsMethodBody(Node)))
or ((Node.Desc=ctnProcedureHead) and (not NodeIsMethodBody(Node.Parent))))
and (not (CurrentIdentifierList.StartAtomBehind.Flag
in [cafEdgedBracketOpen,cafRoundBracketOpen]))
then begin
// for example: procedure DoSomething|
Can:=true;
end
else if Node.Desc in (AllClassBaseSections+AllSourceTypes
+[ctnInterface,ctnImplementation])
then begin
//DebugLn(['TIdentCompletionTool.CheckProcedureDeclarationContext ilcfCanProcDeclaration']);
Can:=true;
end;
if Can then
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfCanProcDeclaration];
end;
begin
Result:=false;
ActivateGlobalWriteLock;
try
try
InitCollectIdentifiers(CursorPos,IdentifierList);
IdentStartXY:=FindIdentifierStartPos(CursorPos);
if CheckCursorInCompilerDirective(IdentStartXY) then exit(true);
if not ParseSourceTillCollectionStart(IdentStartXY,CleanCursorPos,CursorNode,
IdentStartPos,IdentEndPos) then
Exit;
Params:=TFindDeclarationParams.Create(Self,CursorNode);
try
if CleanCursorPos=0 then ;
if IdentStartPos>0 then begin
MoveCursorToCleanPos(IdentStartPos);
ReadNextAtom;
CurrentIdentifierList.StartAtom:=CurPos;
end;
MoveCursorToCleanPos(IdentStartPos);
ReadPriorAtom;
IdentifierPath := '';
while CurPos.Flag = cafPoint do
begin
ReadPriorAtom;
if CurPos.Flag <> cafWord then
Break;
IdentifierPath := GetUpAtom + '.' + IdentifierPath;
ReadPriorAtom;
end;
// find context
GatherContext:=CreateFindContext(Self,CursorNode);
{$IFDEF CTDEBUG}
DebugLn('TIdentCompletionTool.GatherIdentifiers B',
' CleanCursorPos=',CleanPosToStr(CleanCursorPos),
' IdentStartPos=',CleanPosToStr(IdentStartPos),' IdentEndPos=',CleanPosToStr(IdentEndPos),
' Ident=',copy(Src,IdentStartPos,IdentEndPos-IdentStartPos),
' GatherContext=',FindContextToString(GatherContext));
{$ENDIF}
CurrentIdentifierList.NewMemberVisibility:=GetClassVisibility(CursorNode);
if CursorNode.Desc in [ctnUsesSection,ctnUseUnit,ctnUseUnitNamespace,ctnUseUnitClearName] then begin
GatherUnitNames(IdentifierPath);
MoveCursorToCleanPos(IdentEndPos);
ReadNextAtom;
if (CurPos.Flag=cafWord) and (not UpAtomIs('IN')) then begin
// add comma
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfNeedsEndComma];
end;
end else if (CursorNode.Desc in AllSourceTypes)
and (PositionsInSameLine(Src,CursorNode.StartPos,IdentStartPos)) then begin
GatherSourceNames(GatherContext);
end else begin
FindCollectionContext(Params,IdentStartPos,CursorNode,
ExprType,ContextExprStartPos,StartInSubContext,
HasInheritedKeyword);
//debugln(['TIdentCompletionTool.GatherIdentifiers FindCollectionContext ',ExprTypeToString(ExprType)]);
GatherContext := ExprType.Context;
// find class and ancestors if existing (needed for protected identifiers)
if (GatherContext.Tool = Self) or HasInheritedKeyword then
begin
FindContextClassAndAncestorsAndExtendedClassOfHelper(IdentStartXY, FICTClassAndAncestorsAndExtClassOfHelper);
end;
// check for incomplete context
// context bracket level
CurrentIdentifierList.StartBracketLvl:=
GetBracketLvl(Src,CursorNode.StartPos,IdentStartPos,
Scanner.NestedComments);
if CursorNode.Desc in AllPascalStatements then begin
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfStartInStatement];
end;
// context in front of
StartPosOfVariable:=FindStartOfTerm(IdentStartPos,NodeTermInType(CursorNode));
if StartPosOfVariable>0 then begin
if StartPosOfVariable=IdentStartPos then begin
// cursor is at start of an operand
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfStartOfOperand];
end else begin
MoveCursorToCleanPos(IdentStartPos);
ReadPriorAtom;
if CurPos.Flag=cafPoint then
// cursor is behind a point
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfStartIsSubIdent];
end;
MoveCursorToCleanPos(StartPosOfVariable);
ReadPriorAtom;
CurrentIdentifierList.StartAtomInFront:=CurPos;
if (ilcfStartInStatement in CurrentIdentifierList.ContextFlags)
then begin
// check if LValue
if (CurPos.Flag in [cafSemicolon,cafEnd,cafColon])
or UpAtomIs('BEGIN')
or UpAtomIs('TRY') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('FOR') or UpAtomIs('DO') or UpAtomIs('THEN')
or UpAtomIs('REPEAT') or UpAtomIs('ASM') or UpAtomIs('ELSE')
then begin
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfStartOfStatement];
end;
// check if expression
if UpAtomIs('IF') or UpAtomIs('CASE') or UpAtomIs('WHILE')
or UpAtomIs('UNTIL')
then begin
// todo: check at start of expression, not only in front of variable
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfIsExpression, ilcfDontAllowProcedures];
end;
// check if procedure is allowed
if (CurPos.Flag in [cafEdgedBracketOpen, cafEqual, cafOtherOperator])
or ((Scanner.CompilerMode<>cmDelphi) and (CurPos.Flag in [cafAssignment, cafComma, cafRoundBracketOpen])) // "MyEvent := MyProc;" and "o.Test(MyProc)" is supported only in Delphi mode
then
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfDontAllowProcedures];
end;
end;
// context behind
if (IdentEndPos<SrcLen) then begin
MoveCursorToCleanPos(IdentEndPos);
//debugln(['TIdentCompletionTool.GatherIdentifiers "',dbgstr(Src,IdentStartPos,IdentEndPos-IdentStartPos),'"']);
InFrontOfDirective:=(CurPos.StartPos<SrcLen) and (Src[CurPos.StartPos]='{')
and (Src[CurPos.StartPos+1]='$');
ReadNextAtom;
// check end of line
if (not InFrontOfDirective)
and (CursorPos.Code.LineColIsOutside(CursorPos.Y,CursorPos.X)
or (not PositionsInSameLine(Src,IdentEndPos,CurPos.StartPos)))
then
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfEndOfLine];
CurrentIdentifierList.StartAtomBehind:=CurPos;
// check if a semicolon is needed or forbidden at the end
if InFrontOfDirective
or (CurrentIdentifierList.StartBracketLvl>0)
or (CurPos.Flag in [cafSemicolon, cafEqual, cafColon, cafComma,
cafPoint, cafRoundBracketOpen, cafRoundBracketClose,
cafEdgedBracketOpen, cafEdgedBracketClose])
or ((CurPos.Flag in [cafWord,cafNone])
and (UpAtomIs('ELSE')
or UpAtomIs('THEN')
or UpAtomIs('DO')
or UpAtomIs('TO')
or UpAtomIs('OF')
or WordIsBinaryOperator.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)))
then begin
// do not add semicolon
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfNoEndSemicolon];
end;
// check if in statement
if (ilcfStartInStatement in CurrentIdentifierList.ContextFlags) then
begin
// check if a semicolon is needed at the end
if (not (ilcfNoEndSemicolon in CurrentIdentifierList.ContextFlags))
then begin
// check if a semicolon is needed at the end
if (CurPos.Flag in [cafEnd])
or WordIsBlockKeyWord.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)
or ((CurPos.Flag=cafWord)
and (not PositionsInSameLine(Src,IdentEndPos,CurPos.StartPos)))
then begin
// add semicolon
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfNeedsEndSemicolon];
end;
end;
end;
// check missing 'do' after 'with'
if CurrentIdentifierList.StartUpAtomInFrontIs('WITH')
and (not CurrentIdentifierList.StartUpAtomBehindIs('DO'))
and (not CurrentIdentifierList.StartUpAtomBehindIs('AS'))
and (CurrentIdentifierList.StartBracketLvl=0)
and (not (CurrentIdentifierList.StartAtomBehind.Flag in
[cafComma,cafPoint,cafRoundBracketOpen,cafEdgedBracketOpen]))
and (not CurrentIdentifierList.StartUpAtomBehindIs('^'))
then
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfNeedsDo];
end else begin
// end of source
CurrentIdentifierList.ContextFlags:=
CurrentIdentifierList.ContextFlags+[ilcfEndOfLine];
end;
CursorContext:=CreateFindContext(Self,CursorNode);
GatherContextKeywords(CursorContext, IdentStartPos, Beautifier, GatherContext); //note: coth:
// search and gather identifiers in context
if (GatherContext.Tool<>nil) and (GatherContext.Node<>nil) then begin
{$IFDEF CTDEBUG}
DebugLn('TIdentCompletionTool.GatherIdentifiers D CONTEXT: ',
GatherContext.Tool.MainFilename,
' ',GatherContext.Node.DescAsString,
' "',StringToPascalConst(copy(GatherContext.Tool.Src,GatherContext.Node.StartPos,50)),'"');
{$ENDIF}
// gather all identifiers in context
Params.ContextNode:=GatherContext.Node;
Params.SetIdentifier(Self,nil,@CollectAllIdentifiers);
Params.Flags:=[fdfSearchInAncestors,fdfCollect,fdfFindVariable,fdfSearchInHelpers];
if (Params.ContextNode.Desc=ctnInterface) and StartInSubContext then
Include(Params.Flags,fdfIgnoreUsedUnits);
if not StartInSubContext then
Include(Params.Flags,fdfSearchInParentNodes);
if Params.ContextNode.Desc in AllClasses then
Exclude(Params.Flags,fdfSearchInParentNodes);
{$IFDEF CTDEBUG}
DebugLn('TIdentCompletionTool.GatherIdentifiers F');
{$ENDIF}
CurrentIdentifierList.Context:=GatherContext;
if GatherContext.Node.Desc=ctnIdentifier then
Params.Flags:=Params.Flags+[fdfIgnoreCurContextNode];
GatherContext.Tool.FindIdentifierInContext(Params);
end else
if ExprType.Desc in xtAllTypeHelperTypes then
begin
// gather all identifiers in cursor context for basic types (strings etc.)
Params.ContextNode:=CursorNode;
Params.SetIdentifier(Self,nil,@CollectAllIdentifiers);
Params.Flags:=[fdfSearchInAncestors,fdfCollect,fdfFindVariable,fdfSearchInHelpers];
CurrentIdentifierList.Context:=CursorContext;
FindIdentifierInBasicTypeHelpers(ExprType.Desc, Params);
end;
// check for procedure/method declaration context
CheckProcedureDeclarationContext;
// add useful identifiers
{$IFDEF CTDEBUG}
DebugLn('TIdentCompletionTool.GatherIdentifiers G');
{$ENDIF}
GatherUsefulIdentifiers(IdentStartPos,CursorContext,GatherContext);
end;
Result:=true;
finally
FreeListOfPFindContext(FICTClassAndAncestorsAndExtClassOfHelper);
FreeAndNil(FIDCTFoundPublicProperties);
Params.Free;
ClearIgnoreErrorAfter;
end;
finally
GatherUserIdentifiers(CurrentIdentifierList.ContextFlags);
end;
finally
DeactivateGlobalWriteLock;
CurrentIdentifierList:=nil;
end;
{$IFDEF CTDEBUG}
DebugLn(['TIdentCompletionTool.GatherIdentifiers END ']);
{$ENDIF}
end;
function TIdentCompletionTool.FindCodeContext(const CursorPos: TCodeXYPosition;
out CodeContexts: TCodeContextInfo): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
Params: TFindDeclarationParams;
procedure AddPredefinedProcs(CurrentContexts: TCodeContextInfo;
ProcNameAtom: TAtomPosition);
procedure AddCompilerProc(const AProcName: string;
const Params: string; const ResultType: string = '');
var
i: LongInt;
Item: TCodeContextInfoItem;
begin
if CompareIdentifiers(PChar(AProcName),@Src[ProcNameAtom.StartPos])<>0
then exit;
i:=CurrentContexts.AddCompilerProc;
Item:=CurrentContexts[i];
Item.ProcName:=AProcName;
Item.ResultType:=ResultType;
Item.Params:=TStringList.Create;
Item.Params.Delimiter:=';';
Item.Params.StrictDelimiter:=true;
Item.Params.DelimitedText:=Params;
end;
var
IsPointedSystem: Boolean = False;
FPCFullVersion: LongInt;
begin
MoveCursorToAtomPos(ProcNameAtom);
ReadPriorAtom;
if (CurPos.Flag = cafPoint) then
begin
ReadPriorAtom;
IsPointedSystem := UpAtomIs('SYSTEM');
end;
if (CurPos.Flag in [cafEnd,cafSemicolon,cafEqual,cafComma,cafColon,
cafRoundBracketOpen,cafEdgedBracketOpen,cafAssignment,cafOtherOperator])
or IsPointedSystem
or UpAtomIs('BEGIN')
or UpAtomIs('TRY') or UpAtomIs('FINALLY') or UpAtomIs('EXCEPT')
or UpAtomIs('ASM')
or UpAtomIs('REPEAT') or UpAtomIs('UNTIL') or UpAtomIs('WHILE') or UpAtomIs('DO')
or UpAtomIs('IF') or UpAtomIs('THEN') or UpAtomIs('ELSE')
then begin
// see fpc/compiler/psystem.pp
FPCFullVersion:=StrToIntDef(Scanner.Values['FPC_FULLVERSION'],0);
AddCompilerProc('Assert','Condition:Boolean;const Message:String');
AddCompilerProc('Assigned','P:Pointer','Boolean');
AddCompilerProc('Addr','var X','Pointer');
AddCompilerProc('BitSizeOf','Identifier','Integer');
AddCompilerProc('Concat','S1:String;S2:String[...;Sn:String]', 'String');
if FPCFullVersion>=30100 then // FromPosition and Count parameters are optional
begin
AddCompilerProc('Concat','A1:Array[;...An:Array]', 'Array');
AddCompilerProc('Copy','const S:string[;FromPosition,Count:Integer]', 'string');
AddCompilerProc('Copy','const A:array[;FromPosition,Count:Integer]', 'string');
end else
begin
AddCompilerProc('Copy','const S:string;FromPosition,Count:Integer', 'string');
AddCompilerProc('Copy','const A:array;FromPosition,Count:Integer', 'string');
end;
AddCompilerProc('Dec','var X:Ordinal;N:Integer=1');
AddCompilerProc('Default','T:Type','const');
AddCompilerProc('Dispose','var X:Pointer');
AddCompilerProc('Exclude','var S:Set;X:Ordinal');
AddCompilerProc('Exit','ResultValue:Ordinal=Result');
AddCompilerProc('Finalize','var X');
AddCompilerProc('get_frame','','Pointer');
AddCompilerProc('High','Arg:TypeOrVariable','Ordinal');
if FPCFullVersion>=30100 then //Delete and Insert are available as intrinsic since FPC 3.1
begin
AddCompilerProc('Delete','var S:string;Index,Count:Integer');
AddCompilerProc('Delete','var A:array;Index,Count:Integer');
AddCompilerProc('Insert','const Source:string;var Dest:string;Index:Integer');
AddCompilerProc('Insert','Item; var A:array;Index:Integer');
end;
AddCompilerProc('Inc','var X:Ordinal;N:Integer=1');
AddCompilerProc('Include','var S:Set;X:Ordinal');
AddCompilerProc('Initialize','var X');
AddCompilerProc('Length','S:String','Integer');
AddCompilerProc('Length','A:Array','Integer');
AddCompilerProc('Low','Arg:TypeOrVariable','Ordinal');
AddCompilerProc('New','var X:Pointer');
AddCompilerProc('Ofs','var X','LongInt');
AddCompilerProc('Ord','X:Ordinal', 'Integer');
AddCompilerProc('Pack','A:Array;N:Integer;var A:Array');
AddCompilerProc('Pred','X:Ordinal', 'Ordinal');
AddCompilerProc('Read','');
AddCompilerProc('ReadLn','');
AddCompilerProc('ReadStr','S:String;var Args:Arguments');
AddCompilerProc('Seg','var X','LongInt');
AddCompilerProc('SetLength','var S:String;NewLength:Integer');
AddCompilerProc('SetLength','var A:Array;NewLength:Integer');
if Scanner.Values.IsDefined('FPC_HAS_CPSTRING') then begin
AddCompilerProc('SetString','out S:RawByteString;Buf:PAnsiChar;Len:SizeInt');
AddCompilerProc('SetString','out S:AnsiString;Buf:PAnsiChar;Len:SizeInt');
AddCompilerProc('SetString','out S:AnsiString;Buf:PWideChar;Len:SizeInt');
AddCompilerProc('SetString','out S:ShortString;Buf:PChar;Len:SizeInt');
AddCompilerProc('SetString','out S:UnicodeString;Buf:PUnicodeChar;Len:SizeInt');
AddCompilerProc('SetString','out S:UnicodeString;Buf:PChar;Len:SizeInt');
AddCompilerProc('SetString','out S:WideString;Buf:PWideChar;Len:SizeInt');
AddCompilerProc('SetString','out S:WideString;Buf:PChar;Len:SizeInt');
end;
AddCompilerProc('SizeOf','Identifier','Integer');
AddCompilerProc('Slice','var A:Array;Count:Integer','Array');
AddCompilerProc('Str','const X[:Width[:Decimals]];var S:String');
AddCompilerProc('Succ','X:Ordinal', 'Ordinal');
AddCompilerProc('TypeInfo','Identifier', 'Pointer');
AddCompilerProc('GetTypeKind','Identifier', 'TTypeKind');
AddCompilerProc('IsManagedType','Identifier', 'Boolean');
AddCompilerProc('IsConstValue','const Value', 'Boolean');
AddCompilerProc('TypeOf','Identifier', 'Pointer');
AddCompilerProc('Val','S:String;var V;var Code:Integer');
AddCompilerProc('Unaligned','var X','var');
AddCompilerProc('Unpack','A:Array;var A:Array;N:Integer');
AddCompilerProc('Write','Args:Arguments');
AddCompilerProc('WriteLn','Args:Arguments');
AddCompilerProc('WriteStr','var S:String;Args:Arguments');
end;
end;
function CheckContextIsParameter(var Ok: boolean): boolean;
// returns true, on error or context is parameter
var
VarNameAtom, ProcNameAtom: TAtomPosition;
ParameterIndex, StartPos: integer;
ContextExprStartPos: LongInt;
StartInSubContext, HasInheritedKeyword, IsAttributeParams: Boolean;
ExprType: TExpressionType;
AttribParamNode: TCodeTreeNode;
begin
Result:=false;
IsAttributeParams:=false;
if (CursorNode.Desc=ctnParamsRound)
and (CursorNode.Parent.Desc=ctnAttribParam) then begin
IsAttributeParams:=true;
AttribParamNode:=CursorNode.Parent;
StartPos:=AttribParamNode.StartPos;
end else if CursorNode.GetNodeOfTypes([ctnBeginBlock,ctnInitialization,ctnFinalization])<>nil
then begin
StartPos:=CursorNode.StartPos;
end else begin
// not in a begin..end block
DebugLn(['TIdentCompletionTool.FindCodeContext.CheckContextIsParameter not in a begin block "',CursorNode.DescAsString,'"']);
exit;
end;
// check if cursor is in a parameter list
if not CheckParameterSyntax(StartPos, CleanCursorPos,
VarNameAtom, ProcNameAtom, ParameterIndex)
then begin
if VarNameAtom.StartPos=0 then ;
DebugLn(['TIdentCompletionTool.FindCodeContext.CheckContextIsParameter not in a parameter list']);
exit;
end;
//DebugLn('CheckContextIsParameter Variable=',GetAtom(VarNameAtom),' Proc=',GetAtom(ProcNameAtom),' ParameterIndex=',dbgs(ParameterIndex));
// it is a parameter -> create context
Result:=true;
if CurrentIdentifierContexts=nil then
CurrentIdentifierContexts:=TCodeContextInfo.Create;
CurrentIdentifierContexts.Tool:=Self;
CurrentIdentifierContexts.ParameterIndex:=ParameterIndex+1;
CurrentIdentifierContexts.ProcNameAtom:=ProcNameAtom;
CurrentIdentifierContexts.ProcName:=GetAtom(ProcNameAtom);
MoveCursorToAtomPos(ProcNameAtom);
ReadNextAtom; // read opening bracket
CurrentIdentifierContexts.StartPos:=CurPos.EndPos;
// read closing bracket
if ReadTilBracketClose(false) then
CurrentIdentifierContexts.EndPos:=CurPos.StartPos
else
CurrentIdentifierContexts.EndPos:=SrcLen+1;
if IsAttributeParams then begin
debugln(['CheckContextIsParameter AttribParamNode={',ExtractNode(AttribParamNode,[]),'}']);
Params.Flags:=fdfDefaultForExpressions+[fdfSkipClassForward];
Params.Identifier:=@Src[ProcNameAtom.StartPos];
Params.ContextNode:=AttribParamNode.FirstChild;
ExprType:=FindExpressionTypeOfTerm(AttribParamNode.StartPos,ProcNameAtom.EndPos,Params,false);
{$IFDEF VerboseCodeContext}
debugln(['CheckContextIsParameter Attribute: ',ExprTypeToString(ExprType)]);
{$ENDIF}
if (ExprType.Context.Node = nil) or (ExprType.Context.Tool = nil) then
exit;
CurrentIdentifierList.Context:=ExprType.Context;
Params.ContextNode:=ExprType.Context.Node;
Params.Flags:=[fdfSearchInAncestors,fdfCollect,fdfFindVariable,fdfSearchInHelpers];
Params.SetIdentifier(Self,'*',@CollectAttributeConstructors);
ExprType.Context.Tool.FindIdentifierInContext(Params);
end else begin
AddPredefinedProcs(CurrentIdentifierContexts,ProcNameAtom);
FindCollectionContext(Params,ProcNameAtom.StartPos,CursorNode,
ExprType,ContextExprStartPos,StartInSubContext,
HasInheritedKeyword);
if ContextExprStartPos=0 then ;
{$IFDEF VerboseCodeContext}
DebugLn(['CheckContextIsParameter StartInSubContext=',StartInSubContext,' ',ExprTypeToString(ExprType),' "',copy(ExprType.Context.Tool.Src,ExprType.Context.Node.StartPos-20,25),'"']);
{$ENDIF}
if (ExprType.Context.Node = nil) or (ExprType.Context.Tool = nil) then
begin
if ExprType.Desc in xtAllIdentPredefinedTypes then
begin
ExprType.Context.Node := CursorNode;
ExprType.Context.Tool := Self;
end else
Exit;
end;
Params.ContextNode:=ExprType.Context.Node;
if IsAttributeParams then begin
Params.SetIdentifier(Self,'*',@CollectAttributeConstructors);
end else begin
Params.SetIdentifier(Self,@Src[ProcNameAtom.StartPos],@CollectAllContexts);
end;
Params.Flags:=[fdfSearchInAncestors,fdfCollect,fdfFindVariable,fdfSearchInHelpers];
if not StartInSubContext then
Include(Params.Flags,fdfSearchInParentNodes);
CurrentIdentifierList.Context:=ExprType.Context;
{$IFDEF VerboseCodeContext}
DebugLn('CheckContextIsParameter searching procedures, properties and variables ...');
{$ENDIF}
if ExprType.Desc in xtAllTypeHelperTypes then
ExprType.Context.Tool.FindIdentifierInBasicTypeHelpers(ExprType.Desc, Params)
else
ExprType.Context.Tool.FindIdentifierInContext(Params);
end;
// gather declarations of all parameter lists
{$IFDEF VerboseCodeContext}
DebugLn('CheckContextIsParameter END');
{$ENDIF}
Ok:=true;
end;
var
IdentifierList: TIdentifierList;
IdentStartPos, IdentEndPos: integer;
begin
Result:=false;
CodeContexts:=nil;
IdentifierList:=nil;
CurrentIdentifierContexts:=CodeContexts;
ActivateGlobalWriteLock;
try
InitCollectIdentifiers(CursorPos,IdentifierList);
if not ParseSourceTillCollectionStart(CursorPos,CleanCursorPos,CursorNode,
IdentStartPos,IdentEndPos) then
Exit;
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
if IdentStartPos=0 then ;
if IdentEndPos=0 then ;
// find class and ancestors if existing (needed for protected identifiers)
FindContextClassAndAncestorsAndExtendedClassOfHelper(CursorPos,
FICTClassAndAncestorsAndExtClassOfHelper);
if CursorNode<>nil then begin
if not CheckContextIsParameter(Result) then begin
DebugLn(['TIdentCompletionTool.FindCodeContext cursor not at parameter']);
exit;
end;
end;
if CurrentIdentifierContexts=nil then begin
// create default
AddCollectionContext(Self,CursorNode);
end;
Result:=true;
finally
if Result then begin
CodeContexts:=CurrentIdentifierContexts;
CurrentIdentifierContexts:=nil;
end else begin
FreeAndNil(CurrentIdentifierContexts);
end;
FreeListOfPFindContext(FICTClassAndAncestorsAndExtClassOfHelper);
FreeAndNil(FIDCTFoundPublicProperties);
Params.Free;
ClearIgnoreErrorAfter;
end;
finally
DeactivateGlobalWriteLock;
FreeAndNil(CurrentIdentifierList);
end;
end;
function TIdentCompletionTool.FindAbstractMethods(
const CursorPos: TCodeXYPosition; out ListOfPCodeXYPosition: TFPList;
SkipAbstractsInStartClass: boolean): boolean;
const
ProcAttr = [phpWithoutClassKeyword,phpWithHasDefaultValues];
FlagIsAbstract = 0;
FlagIsImplemented = 1;
var
ImplementedInterfaces: TStringToPointerTree;
SearchedAncestors: TAVLTree;
Procs: TAVLTree; // tree of TCodeTreeNodeExtension
procedure AddProc(ATool: TFindDeclarationTool; ProcNode: TCodeTreeNode;
IsAbstract: boolean);
var
ProcText: String;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
ProcText:=ATool.ExtractProcHead(ProcNode,ProcAttr);
AVLNode:=FindCodeTreeNodeExtAVLNode(Procs,ProcText);
if AVLNode<>nil then begin
// known proc
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
//debugln(['AddProc "',ProcText,'" WasImplemented=',NodeExt.Flags=1,' IsAbstract=',IsAbstract]);
if NodeExt.Flags=FlagIsImplemented then
exit; // already implemented
if IsAbstract then
exit; // already abstract
NodeExt.Flags:=FlagIsImplemented;
NodeExt.Node:=ProcNode;
NodeExt.Data:=ATool;
end else begin
// new method
//debugln(['AddProc "',ProcText,'" New IsAbstract=',IsAbstract]);
NodeExt:=TCodeTreeNodeExtension.Create;
NodeExt.Node:=ProcNode;
NodeExt.Data:=ATool;
NodeExt.Txt:=ProcText;
if IsAbstract then
NodeExt.Flags:=FlagIsAbstract
else
NodeExt.Flags:=FlagIsImplemented;
Procs.Add(NodeExt);
end;
end;
procedure CollectImplements(ClassNode: TCodeTreeNode);
var
Node: TCodeTreeNode;
StopNode: TCodeTreeNode;
InterfaceName: String;
begin
Node:=ClassNode.FirstChild;
StopNode:=ClassNode.NextSkipChilds;
while Node<>StopNode do begin
if Node.Desc in AllClassBaseSections then begin
Node:=Node.Next;
continue;
end else if Node.Desc=ctnProperty then begin
if PropertyHasSpecifier(Node,'IMPLEMENTS',false) then begin
ReadNextAtom;
while AtomIsIdentifier do begin
InterfaceName:=GetAtom;
ReadNextAtom;
if CurPos.Flag=cafPoint then begin
ReadNextAtom;
AtomIsIdentifierE(true);
InterfaceName+='.'+GetAtom;
ReadNextAtom;
end;
//debugln(['CollectImplements ',InterfaceName]);
ImplementedInterfaces[InterfaceName]:=Node;
if CurPos.Flag<>cafComma then break;
ReadNextAtom;
end;
end;
end else if Node.Desc=ctnProcedure then begin
if ProcNodeHasSpecifier(Node,psABSTRACT) then begin
if not SkipAbstractsInStartClass then
AddProc(Self,Node,true);
end else begin
AddProc(Self,Node,false);
end;
end;
Node:=Node.NextSkipChilds;
end;
end;
procedure CollectAncestors(aTool: TFindDeclarationTool;
ClassNode: TCodeTreeNode; IsStartClass: boolean); forward;
procedure CollectAncestor(ATool: TFindDeclarationTool;
InheritanceNode: TCodeTreeNode; SearchedAncestors: TAVLTree;
IsStartClass: boolean);
var
Params: TFindDeclarationParams;
ClassNode: TCodeTreeNode;
StopNode: TCodeTreeNode;
Node: TCodeTreeNode;
IsInterface: Boolean;
begin
//debugln(['CollectAncestor Ancestor=',ATool.ExtractIdentifierWithPoints(InheritanceNode.StartPos,false)]);
Params:=TFindDeclarationParams.Create;
try
if not ATool.FindAncestorOfClassInheritance(InheritanceNode,Params,true)
then exit;
ATool:=Params.NewCodeTool;
ClassNode:=Params.NewNode;
if SearchedAncestors.Find(ClassNode)<>nil then
exit; // already searched
SearchedAncestors.Add(ClassNode);
// check all procs of this ancestor
StopNode:=ClassNode.NextSkipChilds;
Node:=ClassNode.FirstChild;
IsInterface:=ClassNode.Desc in AllClassInterfaces;
if IsInterface and (not IsStartClass) then
exit;
while Node<>StopNode do begin
if Node.Desc in AllClassBaseSections then begin
Node:=Node.Next;
continue;
end else if Node.Desc=ctnProcedure then begin
if IsInterface
or ATool.ProcNodeHasSpecifier(Node,psABSTRACT) then
AddProc(ATool,Node,true)
else
AddProc(ATool,Node,false);
end;
Node:=Node.NextSkipChilds;
end;
CollectAncestors(ATool,ClassNode,false);
finally
Params.Free;
end;
end;
procedure CollectAncestors(aTool: TFindDeclarationTool;
ClassNode: TCodeTreeNode; IsStartClass: boolean);
var
InheritanceNode: TCodeTreeNode;
AncestorName: String;
Node: TCodeTreeNode;
begin
//debugln(['CollectAncestors of Class=',aTool.ExtractClassName(ClassNode,false)]);
InheritanceNode:=ATool.FindInheritanceNode(ClassNode);
if (InheritanceNode=nil)
or (InheritanceNode.FirstChild=nil) then begin
// no ancestors
exit;
end;
Node:=InheritanceNode.FirstChild;
while Node<>nil do begin
InheritanceNode:=Node;
Node:=Node.NextBrother;
if InheritanceNode.Desc=ctnIdentifier then begin
if IsStartClass then begin
AncestorName:=ATool.ExtractIdentifierWithPoints(InheritanceNode.StartPos,true);
if ImplementedInterfaces.FindNode(AncestorName)<>nil then
continue;
end;
CollectAncestor(ATool,InheritanceNode,SearchedAncestors,IsStartClass);
end;
end;
end;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
ClassNode: TCodeTreeNode;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
ProcXYPos: TCodeXYPosition;
ATool: TFindDeclarationTool;
begin
Result:=false;
ListOfPCodeXYPosition:=nil;
ImplementedInterfaces:=nil;
Procs:=nil;
SearchedAncestors:=nil;
try
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
// find node at position
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
// if cursor is on type node, find class node
if CursorNode.Desc=ctnTypeDefinition then
CursorNode:=CursorNode.FirstChild
else if CursorNode.Desc=ctnGenericType then
CursorNode:=CursorNode.LastChild
else
CursorNode:=FindClassOrInterfaceNode(CursorNode);
if (CursorNode=nil)
or (not (CursorNode.Desc in AllClassObjects))
or ((CursorNode.SubDesc and ctnsForwardDeclaration)>0) then begin
MoveCursorToCleanPos(CleanCursorPos);
RaiseException(20170421201053,'TIdentCompletionTool.FindAbstractMethods cursor is not in a class');
end;
ClassNode:=CursorNode;
// search class for implemented interfaces and method
ImplementedInterfaces:=TStringToPointerTree.Create(false);
Procs:=TAVLTree.Create(@CompareCodeTreeNodeExt);
CollectImplements(ClassNode);
// search all ancestors
SearchedAncestors:=TAVLTree.Create;
SearchedAncestors.Add(ClassNode);
CollectAncestors(Self,ClassNode,true);
// AddCodePosition for each abstract method
AVLNode:=Procs.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
if NodeExt.Flags=FlagIsAbstract then begin
ATool:=TFindDeclarationTool(NodeExt.Data);
if not ATool.CleanPosToCaret(NodeExt.Node.StartPos,ProcXYPos) then
raise Exception.Create('TIdentCompletionTool.FindAbstractMethods inconsistency');
AddCodePosition(ListOfPCodeXYPosition,ProcXYPos);
end;
AVLNode:=Procs.FindSuccessor(AVLNode);
end;
Result:=true;
finally
DisposeAVLTree(Procs);
ImplementedInterfaces.Free;
SearchedAncestors.Free;
end;
end;
function TIdentCompletionTool.GetValuesOfCaseVariable(
const CursorPos: TCodeXYPosition; List: TStrings; WithTypeDefIfScoped: boolean
): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
CaseAtom: TAtomPosition;
Params: TFindDeclarationParams;
EndPos: LongInt;
ExprType: TExpressionType;
Node: TCodeTreeNode;
Tool: TFindDeclarationTool;
EnumPrefix: string;
begin
Result:=false;
ActivateGlobalWriteLock;
Params:=nil;
try
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
// find node at position
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
// find keyword case
MoveCursorToNodeStart(CursorNode);
CaseAtom:=CleanAtomPosition;
repeat
ReadNextAtom;
if UpAtomIs('CASE') then
CaseAtom:=CurPos
until (CurPos.EndPos>SrcLen) or (CurPos.EndPos>CleanCursorPos);
if CaseAtom.StartPos<1 then begin
debugln(['TIdentCompletionTool.GetValuesOfCaseVariable "case" not found']);
exit;
end;
// find case variable
EndPos:=FindEndOfExpression(CaseAtom.EndPos);
if EndPos>CleanCursorPos then
EndPos:=CleanCursorPos;
//DebugLn(['TIdentCompletionTool.GetValuesOfCaseVariable Expr=',dbgstr(copy(Src,CaseAtom.EndPos,EndPos-CaseAtom.EndPos))]);
Params:=TFindDeclarationParams.Create(Self, CursorNode);
Params.Flags:=fdfDefaultForExpressions+[fdfFunctionResult];
ExprType:=FindExpressionTypeOfTerm(CaseAtom.EndPos,EndPos,Params,true);
//DebugLn(['TIdentCompletionTool.GetValuesOfCaseVariable Type=',ExprTypeToString(ExprType)]);
if ExprType.Desc=xtContext then begin
// resolve aliases and properties
Params.Clear;
Params.Flags:=fdfDefaultForExpressions;
ExprType.Context:=ExprType.Context.Tool.FindBaseTypeOfNode(Params,
ExprType.Context.Node);
end;
case ExprType.Desc of
xtBoolean,
xtByteBool,xtWordBool,xtLongBool,xtQWordBool,
xtBoolean8,xtBoolean16,xtBoolean32,xtBoolean64:
begin
List.Add('True');
List.Add('False');
end;
xtContext:
begin
Node:=ExprType.Context.Node;
Tool:=ExprType.Context.Tool;
if Node=nil then exit;
case Node.Desc of
ctnEnumerationType:
begin
if WithTypeDefIfScoped
and (Tool.Scanner.GetDirectiveValueAt(sdScopedEnums, Node.StartPos) = '1') then
begin
Tool.MoveCursorToCleanPos(Node.Parent.StartPos);
Tool.ReadNextAtom;
EnumPrefix := Tool.GetAtom+'.';
end else
EnumPrefix := '';
Node:=Node.FirstChild;
while Node<>nil do begin
List.Add(EnumPrefix+GetIdentifier(@Tool.Src[Node.StartPos], False));
Node:=Node.NextBrother;
end;
end;
else
debugln(['TIdentCompletionTool.GetValuesOfCaseVariable not an enum: ',Node.DescAsString]);
exit;
end;
end;
else
exit;
end;
Result:=true;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
end;
procedure TIdentCompletionTool.CalcMemSize(Stats: TCTMemStats);
var
Node: TAVLTreeNode;
Ext: TCodeTreeNodeExtension;
m: PtrUint;
begin
inherited CalcMemSize(Stats);
if FICTClassAndAncestorsAndExtClassOfHelper<>nil then
Stats.Add('TIdentCompletionTool.ClassAndAncestorsAndExtClassOfHelper',
FICTClassAndAncestorsAndExtClassOfHelper.Count*(SizeOf(TAVLTreeNode)+SizeOf(TCodeXYPosition)));
if FIDCTFoundPublicProperties<>nil then
Stats.Add('TIdentCompletionTool.FoundPublicProperties',
FIDCTFoundPublicProperties.Count*SizeOf(TAVLTreeNode));
if FIDTFoundMethods<>nil then begin
m:=PtrUint(FIDTFoundMethods.Count)*SizeOf(TAVLTreeNode);
Node:=FIDTFoundMethods.FindLowest;
while Node<>nil do begin
Ext:=TCodeTreeNodeExtension(Node.Data);
inc(m,Ext.CalcMemSize);
Node:=FIDTFoundMethods.FindSuccessor(Node);
end;
STats.Add('TIdentCompletionTool.FoundMethods',m);
end;
if CurrentIdentifierList<>nil then
Stats.Add('TIdentCompletionTool.CurrentIdentifierList',
CurrentIdentifierList.CalcMemSize);
if CurrentIdentifierContexts<>nil then
Stats.Add('TIdentCompletionTool.CurrentContexts',
CurrentIdentifierContexts.CalcMemSize);
end;
{ TIdentifierListItem }
function TIdentifierListItem.GetParamTypeList: string;
var
ANode: TCodeTreeNode;
begin
if not (iliParamTypeListValid in Flags) then begin
// Note: if you implement param lists for other than ctnProcedure, check
// CompareParamList
ANode:=Node;
FParamTypeList:='';
if (ANode<>nil) and (ANode.Desc=ctnProcedure) then begin
try
FParamTypeList:=Tool.ExtractProcHead(ANode,
[phpWithoutClassKeyword,phpWithoutClassName,
phpWithoutName,phpInUpperCase]);
//debugln('TIdentifierListItem.GetParamTypeList A ',GetIdentifier(Identifier),' ',Tool.MainFilename,' ',dbgs(CurNode.StartPos));
except
on E: ECodeToolError do ; // ignore syntax errors
end;
end;
Include(Flags,iliParamTypeListValid);
end;
Result:=FParamTypeList;
end;
function TIdentifierListItem.GetParamNameList: string;
var
ANode: TCodeTreeNode;
begin
if not (iliParamNameListValid in Flags) then begin
// Note: if you implement param lists for other than ctnProcedure, check
// CompareParamList
ANode:=Node;
FParamNameList:='';
if (ANode<>nil) and (ANode.Desc=ctnProcedure) then begin
try
FParamNameList:=Tool.ExtractProcHead(ANode,
[phpWithoutClassKeyword,phpWithoutClassName,
phpWithoutName,phpInUpperCase,phpWithParameterNames]);
//debugln('TIdentifierListItem.GetParamNameList A ',GetIdentifier(Identifier),' ',Tool.MainFilename,' ',dbgs(CurNode.StartPos));
except
on E: ECodeToolError do ; // ignore syntax errors
end;
end;
Include(Flags,iliParamNameListValid);
end;
Result:=FParamNameList;
end;
function TIdentifierListItem.GetNode: TCodeTreeNode;
begin
Result:=nil;
if Tool=nil then
exit;
if (iliNodeValid in Flags)
and (FToolNodesDeletedStep<>Tool.NodesDeletedChangeStep) then
Exclude(Flags,iliNodeValid);
if (not (iliNodeValid in Flags)) then begin
if iliNodeHashValid in Flags then begin
RestoreNode;
if (iliNodeValid in Flags) then begin
Result:=FNode;
end;
end;
end else begin
if FToolNodesDeletedStep=Tool.NodesDeletedChangeStep then begin
Result:=FNode;
end else begin
if not (iliNodeGoneWarned in Flags) then begin
DebugLn(['TIdentifierListItem.GetNode node ',Identifier,' is gone from ',Tool.MainFilename]);
Include(Flags,iliNodeGoneWarned);
end;
FNode:=nil;
end;
end;
end;
procedure TIdentifierListItem.SetNode(const AValue: TCodeTreeNode);
procedure RaiseToolMissing;
begin
raise Exception.Create('TIdentifierListItem.SetNode Node without Tool');
end;
begin
FNode:=AValue;
Include(Flags,iliNodeValid);
Exclude(Flags,iliNodeHashValid);
if (FNode<>nil) and (Tool=nil) then
RaiseToolMissing;
if (Tool<>nil) then
FToolNodesDeletedStep:=Tool.NodesDeletedChangeStep;
if (FNode<>nil) then
StoreNodeHash;
end;
procedure TIdentifierListItem.SetParamTypeList(const AValue: string);
begin
FParamTypeList:=AValue;
Include(Flags,iliParamTypeListValid);
end;
procedure TIdentifierListItem.SetParamNameList(const AValue: string);
begin
FParamNameList:=AValue;
Include(Flags,iliParamNameListValid);
end;
procedure TIdentifierListItem.SetResultType(const AValue: string);
begin
FResultType := AValue;
Include(Flags, iliResultTypeValid);
end;
function TIdentifierListItem.AsString: string;
var
ANode: TCodeTreeNode;
begin
WriteStr(Result, Compatibility);
if HasChilds then
Result:=Result+' HasChilds'
else
Result:=Result+' HasNoChilds';
Result:=Result+' History='+IntToStr(HistoryIndex);
Result:=Result+' Ident='+Identifier;
Result:=Result+' Lvl='+IntToStr(Level);
if Tool<>nil then
Result:=Result+' File='+Tool.MainFilename;
ANode:=Node;
if ANode<>nil then
Result:=Result+' Node='+ANode.DescAsString
+' "'+StringToPascalConst(copy(Tool.Src,ANode.StartPos,50))+'"';
end;
procedure TIdentifierListItem.BeautifyIdentifier(IdentList: TIdentifierList);
begin
// can be overridden
end;
function TIdentifierListItem.GetDesc: TCodeTreeNodeDesc;
var
ANode: TCodeTreeNode;
begin
ANode:=Node;
if ANode<>nil then
Result:=ANode.Desc
else
Result:=DefaultDesc;
end;
constructor TIdentifierListItem.Create(
NewCompatibility: TIdentifierCompatibility; NewHasChilds: boolean;
NewHistoryIndex: integer; NewIdentifier: PChar; NewLevel: integer;
NewNode: TCodeTreeNode; NewTool: TFindDeclarationTool;
NewDefaultDesc: TCodeTreeNodeDesc);
begin
Compatibility:=NewCompatibility;
if NewHasChilds then Include(FLags,iliHasChilds);
HistoryIndex:=NewHistoryIndex;
Identifier:=GetIdentifier(NewIdentifier);
Level:=NewLevel;
Tool:=NewTool;
Node:=NewNode;
DefaultDesc:=NewDefaultDesc;
BaseExprType:=CleanExpressionType;
end;
function TIdentifierListItem.IsProcNodeWithParams: boolean;
var
ANode: TCodeTreeNode;
StartPos: Integer;
begin
Result:=(GetDesc=ctnProcedure);
if not Result then exit;
if (iliParamNameListValid in Flags) then begin
StartPos:=1;
while (StartPos<=length(FParamTypeList))
and (FParamTypeList[StartPos] in [' ',#9,'(','[']) do
inc(StartPos);
if (StartPos<=length(FParamTypeList))
and (FParamTypeList[StartPos] in [')',']',';']) then
exit(false)
else
exit(true);
end else if (iliParamTypeListValid in Flags) then begin
// the type list does not contain names
// so a () could be empty or (var buf)
StartPos:=1;
while (StartPos<=length(FParamTypeList))
and (FParamTypeList[StartPos] in [' ',#9,'(','[']) do
inc(StartPos);
if (StartPos<=length(FParamTypeList))
and (not (FParamTypeList[StartPos] in [')',']',';'])) then
exit(true);
end;
ANode:=Node;
Result:=(ANode<>nil) and Tool.ProcNodeHasParamList(ANode);
end;
function TIdentifierListItem.IsPropertyWithParams: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliHasParamListValid in Flags) then begin
Include(Flags,iliHasParamListValid);
ANode:=Node;
if (ANode<>nil) and Tool.PropertyNodeHasParamList(ANode) then
Include(Flags,iliHasParamList)
else
Exclude(Flags,iliHasParamList);
end;
Result:=iliHasParamList in Flags;
end;
function TIdentifierListItem.IsPropertyReadOnly: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliIsReadOnlyValid in Flags) then begin
Include(Flags,iliIsReadOnlyValid);
ANode:=Node;
if (ANode<>nil) and Tool.PropertyHasSpecifier(ANode,'READ',false)
and not Tool.PropertyHasSpecifier(ANode,'WRITE',false) then
Include(Flags,iliIsReadOnly)
else
Exclude(Flags,iliIsReadOnly);
end;
Result:=iliIsReadOnly in Flags;
end;
function TIdentifierListItem.GetHintModifiers: TPascalHintModifiers;
var
ANode: TCodeTreeNode;
begin
Result:=[];
if not (iliHintModifiersValid in Flags) then begin
Include(Flags,iliHintModifiersValid);
ANode:=Node;
if ANode<>nil then begin
Result:=Tool.GetHintModifiers(ANode);
if phmDeprecated in Result then Include(Flags,iliIsDeprecated);
if phmPlatform in Result then Include(Flags,iliIsPlatform);
if phmLibrary in Result then Include(Flags,iliIsLibrary);
if phmUnimplemented in Result then Include(Flags,iliIsUnimplemented);
if phmExperimental in Result then Include(Flags,iliIsExperimental);
end;
end else begin
if iliIsDeprecated in Flags then Include(Result,phmDeprecated);
if iliIsPlatform in Flags then Include(Result,phmPlatform);
if iliIsLibrary in Flags then Include(Result,phmLibrary);
if iliIsUnimplemented in Flags then Include(Result,phmUnimplemented);
if iliIsExperimental in Flags then Include(Result,phmExperimental);
end;
end;
function TIdentifierListItem.CheckHasChilds: boolean;
// returns true if test was successful
var
ANode: TCodeTreeNode;
begin
Result:=false;
if GetDesc in AllClasses then begin
Result:=true;
exit;
end;
ANode:=Node;
if ANode=nil then exit;
UpdateBaseContext;
if (BaseExprType.Desc=xtContext)
and (BaseExprType.Context.Node<>nil)
and (BaseExprType.Context.Node.Desc in AllClasses)
then
Include(Flags,iliHasChilds);
Result:=true;
end;
function TIdentifierListItem.CanBeAssigned: boolean;
var
ANode: TCodeTreeNode;
begin
Result:=false;
ANode:=Node;
if (ANode=nil) then exit;
if (GetDesc=ctnVarDefinition) then
Result:=true;
if (ANode.Desc in [ctnProperty,ctnGlobalProperty]) then begin
if Tool.PropertyHasSpecifier(ANode,'WRITE') then exit(true);
if Tool.PropNodeIsTypeLess(ANode) then begin
exit(true);// ToDo: search the real property definition
end;
end;
end;
procedure TIdentifierListItem.UpdateBaseContext;
var
Params: TFindDeclarationParams;
ANode: TCodeTreeNode;
begin
if (iliBaseExprTypeValid in Flags) then exit;
Include(Flags,iliBaseExprTypeValid);
BaseExprType:=CleanExpressionType;
BaseExprType.Desc:=xtNone;
ANode:=Node;
if (ANode<>nil) and (Tool<>nil) then begin
Tool.ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Tool, ANode);
try
if ANode.HasParentOfType(ctnGenericType) then exit;
BaseExprType.Context:=Tool.FindBaseTypeOfNode(Params,ANode);
if (BaseExprType.Context.Node<>nil) then
BaseExprType.Desc:=xtContext;
finally
Params.Free;
Tool.DeactivateGlobalWriteLock;
end;
end;
end;
function TIdentifierListItem.HasChilds: boolean;
begin
Result:=iliHasChilds in Flags;
end;
function TIdentifierListItem.HasIndex: boolean;
// check if edged bracket can be used []
var
ANode: TCodeTreeNode;
begin
if not (iliHasIndexValid in Flags) then begin
UpdateBaseContext;
if BaseExprType.Desc in (xtAllStringConvertibles+xtAllWideStringConvertibles)
then begin
// strings, widestrings and PChar
Include(Flags,iliHasIndex);
end else if (BaseExprType.Desc=xtContext) and (BaseExprType.Context.Node<>nil)
then begin
//debugln(['TIdentifierListItem.HasIndex ',BaseExprType.Context.Node.DescAsString]);
ANode:=BaseExprType.Context.Node;
case ANode.Desc of
ctnRangedArrayType,ctnOpenArrayType: Include(Flags,iliHasIndex);
end;
end;
end;
Result:=iliHasIndex in Flags;
end;
function TIdentifierListItem.IsFunction: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliIsFunctionValid in Flags) then
begin
ANode := Node;
if (ANode <> nil) and Tool.NodeIsFunction(ANode) then
Include(Flags, iliIsFunction);
Include(Flags, iliIsFunctionValid);
end;
Result := iliIsFunction in Flags;
end;
function TIdentifierListItem.IsConstructor: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliIsConstructorValid in Flags) then
begin
ANode := Node;
if (ANode <> nil) and Tool.NodeIsConstructor(ANode) then
Include(Flags, iliIsConstructor);
Include(Flags, iliIsConstructorValid);
end;
Result := iliIsConstructor in Flags;
end;
function TIdentifierListItem.IsDestructor: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliIsDestructorValid in Flags) then
begin
ANode := Node;
if (ANode <> nil) and Tool.NodeIsDestructor(ANode) then
Include(Flags, iliIsDestructor);
Include(Flags, iliIsDestructorValid);
end;
Result := iliIsDestructor in Flags;
end;
function TIdentifierListItem.IsAbstractMethod: boolean;
var
ANode: TCodeTreeNode;
begin
if not (iliIsAbstractMethodValid in Flags) then begin
ANode:=Node;
if (ANode<>nil)
and Tool.ProcNodeHasSpecifier(ANode,psABSTRACT) then
Include(Flags,iliIsAbstractMethod);
Include(Flags,iliIsAbstractMethodValid);
end;
Result:=iliIsAbstractMethod in Flags;
end;
function TIdentifierListItem.TryIsAbstractMethod: boolean;
begin
try
Result:=IsAbstractMethod;
except
Result:=false;
end;
end;
procedure TIdentifierListItem.Clear;
begin
FParamTypeList:='';
FResultType:='';
Compatibility:=icompUnknown;
HistoryIndex:=0;
Identifier:='';
Level:=0;
FNode:=nil;
Tool:=nil;
DefaultDesc:=ctnNone;
Flags:=[];
BaseExprType:=CleanExpressionType;
end;
procedure TIdentifierListItem.UnbindNode;
begin
if FNode=nil then exit;
StoreNodeHash;
Exclude(Flags,iliNodeValid);
FNode:=nil;
end;
procedure TIdentifierListItem.StoreNodeHash;
begin
Include(Flags,iliNodeHashValid);
FNodeStartPos:=FNode.StartPos;
FNodeDesc:=FNode.Desc;
FNodeHash:=GetNodeHash(FNode);
//DebugLn(['TIdentifierListItem.StoreNodeHash ',Identifier,' Pos=',FNodeStartPos,' Hash=',FNodeHash]);
end;
function TIdentifierListItem.RestoreNode: boolean;
var
NewNode: TCodeTreeNode;
NewHash: Cardinal;
begin
if not (iliNodeHashValid in Flags) then exit(true);
//DebugLn(['TIdentifierListItem.RestoreNode ',Identifier]);
NewNode:=Tool.BuildSubTreeAndFindDeepestNodeAtPos(FNodeStartPos,false);
Result:=false;
if (NewNode=nil) or (NewNode.StartPos<>FNodeStartPos)
or (NewNode.Desc<>FNodeDesc) then begin
DebugLn(['TIdentifierListItem.RestoreNode not found: ',Identifier]);
Exclude(Flags,iliNodeHashValid);
exit;
end;
NewHash:=GetNodeHash(NewNode);
if NewHash<>FNodeHash then begin
DebugLn(['TIdentifierListItem.RestoreNode hash changed: ',Identifier]);
Exclude(Flags,iliNodeHashValid);
exit;
end;
//DebugLn(['TIdentifierListItem.RestoreNode Success ',Identifier]);
Node:=NewNode;
Result:=true;
end;
function TIdentifierListItem.GetNodeHash(ANode: TCodeTreeNode): Cardinal;
var
StartPos: LongInt;
EndPos: LongInt;
begin
case ANode.Desc of
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnGenericType:
ANode:=Tool.FindDefinitionNameNode(ANode);
end;
if ANode<>nil then
begin
StartPos:=ANode.StartPos;
EndPos:=StartPos+20;
if EndPos>ANode.EndPos then EndPos:=ANode.EndPos;
Result:=crc32(0, PByte(@Tool.Src[StartPos]), EndPos-StartPos);
end else
Result:=0;
end;
function TIdentifierListItem.CompareParamList(CompareItem: TIdentifierListItem
): integer;
var
ANode: TCodeTreeNode;
CmpNode: TCodeTreeNode;
begin
Result:=0;
if Self=CompareItem then exit;
ANode:=Node;
CmpNode:=CompareItem.Node;
if (ANode=CmpNode) then exit;
if (ANode=nil) or (CmpNode=nil) then exit;
if (ANode.Desc<>ctnProcedure) or (CmpNode.Desc<>ctnProcedure) then
exit;
{DbgOut('TIdentifierListItem.CompareParamList ',GetIdentifier(Identifier),'=',GetIdentifier(CompareItem.Identifier));
if Node<>nil then
DbgOut(' Self=',Tool.MainFilename,' ',dbgs(Node.StartPos));
if CompareItem.Node<>nil then
DbgOut(' Other=',CompareItem.Tool.MainFilename,' ',dbgs(CompareItem.Node.StartPos));
debugln('');}
Result:=CompareTextIgnoringSpace(ParamTypeList,CompareItem.ParamTypeList,false);
end;
function TIdentifierListItem.CompareParamList(
CompareItem: TIdentifierListSearchItem): integer;
begin
if (ParamTypeList='') and (CompareItem.ParamList='') then
exit(0);
Result:=CompareTextIgnoringSpace(ParamTypeList,CompareItem.ParamList,false);
end;
function TIdentifierListItem.CalcMemSize: PtrUInt;
begin
Result:=PtrUInt(InstanceSize)
+MemSizeString(FParamTypeList)
+SizeOf(FNodeHash)
+MemSizeString(Identifier);
end;
{ TIdentifierHistoryList }
procedure TIdentifierHistoryList.SetCapacity(const AValue: integer);
begin
if FCapacity=AValue then exit;
FCapacity:=AValue;
if FCapacity<1 then FCapacity:=1;
while (FItems.Count>0) and (FItems.Count>=FCapacity) do
FItems.FreeAndDelete(FItems.FindHighest);
end;
function TIdentifierHistoryList.FindItem(NewItem: TIdentifierListItem
): TAVLTreeNode;
begin
if NewItem<>nil then
Result:=FItems.FindKey(NewItem,@CompareIdentItemWithHistListItem)
else
Result:=nil;
end;
constructor TIdentifierHistoryList.Create;
begin
FItems:=TAVLTree.Create(@CompareIdentHistListItem);
FCapacity:=30;
end;
destructor TIdentifierHistoryList.Destroy;
begin
Clear;
FItems.Free;
inherited Destroy;
end;
procedure TIdentifierHistoryList.Clear;
begin
FItems.FreeAndClear;
end;
procedure TIdentifierHistoryList.Add(NewItem: TIdentifierListItem);
var
OldAVLNode: TAVLTreeNode;
NewHistItem: TIdentHistListItem;
AnAVLNode: TAVLTreeNode;
AdjustIndex: Integer;
AnHistItem: TIdentHistListItem;
begin
if NewItem=nil then exit;
OldAVLNode:=FindItem(NewItem);
{$IFDEF ShowHistory}
DebugLn(['TIdentifierHistoryList.Add Count=',Count,' Found=',OldAVLNode<>nil,
' ITEM: ',NewItem.AsString]);
{$ENDIF}
if OldAVLNode<>nil then begin
// already in tree
NewHistItem:=TIdentHistListItem(OldAVLNode.Data);
if NewHistItem.HistoryIndex=0 then exit;
// must be moved -> remove it from the tree
AdjustIndex:=NewHistItem.HistoryIndex;
FItems.Delete(OldAVLNode);
end else begin
// create a new history item
NewHistItem:=TIdentHistListItem.Create;
NewHistItem.Identifier:=NewItem.Identifier;
NewHistItem.NodeDesc:=NewItem.GetDesc;
NewHistItem.ParamList:=NewItem.ParamTypeList;
AdjustIndex:=high(AdjustIndex);
end;
NewHistItem.HistoryIndex:=0;
// adjust all other HistoryIndex
AnAVLNode:=Fitems.FindLowest;
while AnAVLNode<>nil do begin
AnHistItem:=TIdentHistListItem(AnAVLNode.Data);
if AnHistItem.HistoryIndex<AdjustIndex then
inc(AnHistItem.HistoryIndex);
AnAVLNode:=FItems.FindSuccessor(AnAVLNode);
end;
if (FItems.Count>0) and (FItems.Count>=FCapacity) then
FItems.FreeAndDelete(FItems.FindHighest);
FItems.Add(NewHistItem);
{$IFDEF ShowHistory}
DebugLn(['TIdentifierHistoryList.Added Count=',Count]);
{$ENDIF}
end;
function TIdentifierHistoryList.GetHistoryIndex(AnItem: TIdentifierListItem
): integer;
var
AnAVLNode: TAVLTreeNode;
begin
AnAVLNode:=FindItem(AnItem);
if AnAVLNode=nil then
Result:=33333333 // a very high value
else
Result:=TIdentHistListItem(AnAVLNode.Data).HistoryIndex;
end;
function TIdentifierHistoryList.Count: integer;
begin
Result:=FItems.Count;
end;
function TIdentifierHistoryList.CalcMemSize: PtrUInt;
var
Node: TAVLTreeNode;
Item: TIdentHistListItem;
begin
Result:=PtrUInt(InstanceSize);
if FItems<>nil then begin
{%H-}inc(Result,FItems.Count*SizeOf(TAVLTreeNode));
Node:=FItems.FindLowest;
while Node<>nil do begin
Item:=TIdentHistListItem(Node.Data);
inc(Result,Item.CalcMemSize);
Node:=FItems.FindSuccessor(Node);
end;
end;
end;
{ TCodeContextInfo }
function TCodeContextInfo.GetItems(Index: integer): TCodeContextInfoItem;
begin
Result:=TCodeContextInfoItem(FItems[Index]);
end;
constructor TCodeContextInfo.Create;
begin
FItems:=TFPList.Create;
end;
destructor TCodeContextInfo.Destroy;
begin
Clear;
FreeAndNil(FItems);
inherited Destroy;
end;
function TCodeContextInfo.Count: integer;
begin
Result:=FItems.Count;
end;
function TCodeContextInfo.Add(const Context: TExpressionType): integer;
var
Item: TCodeContextInfoItem;
begin
Item:=TCodeContextInfoItem.Create;
Item.Expr:=Context;
Result:=FItems.Add(Item);
end;
function TCodeContextInfo.AddCompilerProc: integer;
var
Item: TCodeContextInfoItem;
begin
Item:=TCodeContextInfoItem.Create;
Result:=FItems.Add(Item);
end;
procedure TCodeContextInfo.Clear;
var
i: Integer;
begin
for i:=0 to FItems.Count-1 do
TObject(FItems[i]).Free;
FItems.Clear;
end;
function TCodeContextInfo.CalcMemSize: PtrUInt;
begin
Result:=PtrUInt(InstanceSize)
+{%H-}PtrUInt(TCodeContextInfoItem)*SizeOf(FItems.Count)
+MemSizeString(FProcName);
end;
{ TIdentifierListSearchItem }
function TIdentifierListSearchItem.CalcMemSize: PtrUInt;
begin
Result:=PtrUInt(InstanceSize)
+MemSizeString(ParamList);
end;
{ TIdentHistListItem }
function TIdentHistListItem.CalcMemSize: PtrUInt;
begin
Result:=PtrUInt(InstanceSize)
+MemSizeString(Identifier)
+MemSizeString(ParamList);
end;
{ TCodeContextInfoItem }
destructor TCodeContextInfoItem.Destroy;
begin
FreeAndNil(Params);
inherited Destroy;
end;
function TCodeContextInfoItem.AsDebugString(WithExpr: boolean): string;
var
i: Integer;
begin
Result:=ProcName+'(';
if Params<>nil then
for i:=0 to Params.Count-1 do begin
if i>0 then Result+=',';
Result+=Params[i];
end;
Result+=')';
if ResultType<>'' then Result+=':'+ResultType;
if WithExpr then
Result+=' '+ExprTypeToString(Expr);
end;
end.