diff --git a/src/protocol/LSP.Basic.pas b/src/protocol/LSP.Basic.pas index c36c0b3..06ae990 100644 --- a/src/protocol/LSP.Basic.pas +++ b/src/protocol/LSP.Basic.pas @@ -26,7 +26,7 @@ interface uses - FPJson, + FPJson, fgl, Classes, SysUtils, LSP.BaseTypes, LSP.Messages; type @@ -78,6 +78,7 @@ TRange = class(TLSPStreamable) constructor Create(startLine, startColumn: integer; endLine, endColumn: integer); overload; Procedure SetRange(line, column: integer; len: integer = 0); overload; Procedure SetRange(startLine, startColumn: integer; endLine, endColumn: integer); overload; + function InRange(line, column: integer; len: integer = 0): Boolean; Destructor destroy; override; Procedure Assign(Source : TPersistent); override; function ToString: String; override; @@ -378,7 +379,7 @@ TDiagnostic = class (TCollectionItem) fSeverity: TDiagnosticSeverity; fCode: TOptionalInteger; fSource: TOptionalString; - fMessage: string; + fMessage: TOptionalString; procedure SetRange(AValue: TRange); Public Constructor Create(ACollection: TCollection); override; @@ -396,7 +397,7 @@ TDiagnostic = class (TCollectionItem) // diagnostic, e.g. 'typescript' or 'super lint'. property source: TOptionalString read fSource write fSource; // The diagnostic's message. - property message: string read fMessage write fMessage; + property message: TOptionalString read fMessage write fMessage; // Additional metadata about the diagnostic. // @since 3.15.0 @@ -410,6 +411,7 @@ TDiagnostic = class (TCollectionItem) end; TDiagnosticItems = specialize TGenericCollection; + TUriDiagnostics = specialize TFPGMapObject; { TCommand https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command @@ -976,6 +978,17 @@ procedure TRange.SetRange(startLine, startColumn: integer; endLine, fEnd.Character:=endColumn; end; +function TRange.InRange(line, column: integer; len: integer): Boolean; +begin + Result := ((fStart.line < line) and (fEnd.line > line)) or + ((fStart.line = line) and (fStart.character <= column) and + ((fEnd.line > line) or (fEnd.character >= column)) + ) or + ((fEnd.line = line) and (fEnd.character >= column) and + ((fStart.line < line) or (fStart.character <= column)) + ); +end; + destructor TRange.destroy; begin FreeAndNil(fStart); @@ -1106,9 +1119,15 @@ procedure TDiagnostic.Assign(Source : TPersistent); Range:=Src.Range; Severity:=Src.severity; Code:=Src.Code; - self.Source:=Src.Source; if Src.source.HasValue then - Message:=Src.Source.Value; + self.Source:=Src.Source.Value + else + self.Source:=Nil; + + if Src.message.HasValue then + self.message:=Src.message.Value + else + self.message:=Nil; end else inherited Assign(Source); diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index 8c5441f..0e9b423 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -69,13 +69,23 @@ TPublishDiagnosticsParams = class(TLSPStreamable) TPublishDiagnostics = class(TNotificationMessage) private + fUserMessages: TDiagnosticItems; + fCodeToolErrors: TUriDiagnostics; + fParserErrors: TUriDiagnostics; + function GetDiagnosticParams: TPublishDiagnosticsParams; public constructor Create; override; destructor Destroy; override; - function HaveDiagnostics : Boolean; + procedure SendDiagnostics(fileName: string; aTransport : TMessageTransport); Property DiagnosticParams : TPublishDiagnosticsParams Read GetDiagnosticParams; + procedure AddCodeToolError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); + procedure AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); + procedure AddUserMessage(message: string; line, column, code: integer; severity: TDiagnosticSeverity); procedure Add(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); + procedure ClearCodeToolErrors(fileName: string); + procedure ClearParserError(fileName: string); + procedure ClearUserMessages; procedure Clear(fileName: string); end; @@ -86,6 +96,127 @@ implementation { TPublishDiagnostics } +procedure TPublishDiagnostics.ClearUserMessages; +begin + DiagnosticParams.uri := ''; + fUserMessages.Clear; +end; + +procedure TPublishDiagnostics.ClearCodeToolErrors(fileName: string); +var + CodeToolErrorsDiagnostics: TDiagnosticItems; +begin + DiagnosticParams.uri := PathToURI(fileName); + if not fCodeToolErrors. + TryGetData(DiagnosticParams.uri, CodeToolErrorsDiagnostics) + then + begin + CodeToolErrorsDiagnostics := TDiagnosticItems.Create; + fCodeToolErrors.Add(DiagnosticParams.uri, CodeToolErrorsDiagnostics); + end; + + CodeToolErrorsDiagnostics.Clear; +end; + +procedure TPublishDiagnostics.ClearParserError(fileName: string); +var + CodeToolErrorsDiagnostics: TDiagnosticItems; +begin + DiagnosticParams.uri := PathToURI(fileName); + if not fParserErrors. + TryGetData(DiagnosticParams.uri, CodeToolErrorsDiagnostics) + then + begin + CodeToolErrorsDiagnostics := TDiagnosticItems.Create; + fParserErrors.Add(DiagnosticParams.uri, CodeToolErrorsDiagnostics); + end; + + CodeToolErrorsDiagnostics.Clear; +end; + +procedure TPublishDiagnostics.AddUserMessage( + message: string; + line, column, code: integer; + severity: TDiagnosticSeverity + ); +var + Diagnostic: TDiagnostic; +begin + DiagnosticParams.uri := ''; + Diagnostic := fUserMessages.Add; + Diagnostic.range.SetRange(line, column); + Diagnostic.severity := severity; + Diagnostic.code := code; + Diagnostic.source := 'Free Pascal Compiler'; + Diagnostic.message := message; +end; + +procedure TPublishDiagnostics.AddCodeToolError( + fileName, message: string; + line, column, code: integer; + severity: TDiagnosticSeverity + ); +var + CodeToolErrorsDiagnostics: TDiagnosticItems; + Diagnostic: TDiagnostic; + i: Integer; +begin + DiagnosticParams.uri := PathToURI(fileName); + if not fCodeToolErrors. + TryGetData(DiagnosticParams.uri, CodeToolErrorsDiagnostics) + then + begin + CodeToolErrorsDiagnostics := TDiagnosticItems.Create; + fCodeToolErrors.Add(DiagnosticParams.uri, CodeToolErrorsDiagnostics); + end; + + i := 0; + while i < CodeToolErrorsDiagnostics.Count do + begin + Diagnostic := CodeToolErrorsDiagnostics.Items[i]; + if Diagnostic.range.InRange(line, column) then + Break; + Inc(i); + end; + + if i >= CodeToolErrorsDiagnostics.Count then + begin + Diagnostic := CodeToolErrorsDiagnostics.Add; + end; + + Diagnostic.range.SetRange(line, column); + Diagnostic.severity := severity; + Diagnostic.code := code; + Diagnostic.source := 'Free Pascal Compiler'; + Diagnostic.message := message; +end; + +procedure TPublishDiagnostics.AddParserError( + fileName, message: string; + line, column, code: integer; + severity: TDiagnosticSeverity + ); +var + CodeToolErrorsDiagnostics: TDiagnosticItems; + Diagnostic: TDiagnostic; +begin + DiagnosticParams.uri := PathToURI(fileName); + if not fParserErrors. + TryGetData(DiagnosticParams.uri, CodeToolErrorsDiagnostics) + then + begin + CodeToolErrorsDiagnostics := TDiagnosticItems.Create; + fParserErrors.Add(DiagnosticParams.uri, CodeToolErrorsDiagnostics); + end; + + Diagnostic := CodeToolErrorsDiagnostics.Add; + Diagnostic.range.SetRange(line, column); + Diagnostic.severity := severity; + Diagnostic.code := code; + Diagnostic.source := 'Free Pascal Compiler'; + Diagnostic.message := message; +end; + procedure TPublishDiagnostics.Clear(fileName: string); begin DiagnosticParams.uri := PathToURI(fileName); @@ -96,7 +227,11 @@ procedure TPublishDiagnostics.Add(fileName, message: string; line, column, code: var Diagnostic: TDiagnostic; begin - DiagnosticParams.uri := PathToURI(fileName); + if Length(fileName) = 0 then + DiagnosticParams.uri := '' + else + DiagnosticParams.uri := PathToURI(fileName); + Diagnostic := DiagnosticParams.diagnostics.Add; Diagnostic.range.SetRange(line, column); Diagnostic.severity := severity; @@ -111,8 +246,63 @@ function TPublishDiagnostics.GetDiagnosticParams: TPublishDiagnosticsParams; Result:=Params as TPublishDiagnosticsParams; end; +procedure TPublishDiagnostics.SendDiagnostics( + fileName: string; + aTransport: TMessageTransport + ); +var + Diagnostic, sentDiagnostic: TDiagnostic; + + procedure IterateDiagnosticItems(uriDiagnostics: TUriDiagnostics); + var + DiagnosticItems: TDiagnosticItems; + begin + if not uriDiagnostics. + TryGetData(PathToURI(fileName), DiagnosticItems) + then + begin + DiagnosticItems := TDiagnosticItems.Create; + uriDiagnostics.Add(PathToURI(fileName), DiagnosticItems); + end; + + for TCollectionItem(Diagnostic) in DiagnosticItems do + begin + sentDiagnostic := DiagnosticParams.diagnostics.Add; + sentDiagnostic.Assign(Diagnostic); + end; + end; + +begin + DiagnosticParams.diagnostics.Clear; + // if fUserMessages.count > 0 add to DiagnosticParams.diagnostics + if Length(fileName) = 0 then + begin + DiagnosticParams.uri := ''; + for TCollectionItem(Diagnostic) in fUserMessages do + begin + sentDiagnostic := DiagnosticParams.diagnostics.Add; + sentDiagnostic.Assign(Diagnostic); + end; + end + else + begin + DiagnosticParams.uri := PathToURI(fileName); + + // loop over all fCodeToolErrors[fileName] and fParserErrors[fileName] + // add to DiagnosticParams.diagnostics + IterateDiagnosticItems(fCodeToolErrors); + IterateDiagnosticItems(fParserErrors); + end; + + Send(aTransport); +end; + constructor TPublishDiagnostics.Create; begin + fUserMessages := TDiagnosticItems.Create; + fCodeToolErrors := TUriDiagnostics.Create(True); + fParserErrors := TUriDiagnostics.Create(True); + params := TPublishDiagnosticsParams.Create; method := 'textDocument/publishDiagnostics'; end; @@ -120,14 +310,13 @@ constructor TPublishDiagnostics.Create; destructor TPublishDiagnostics.Destroy; begin params.Free; + fCodeToolErrors.Free; + fUserMessages.Free; + fParserErrors.Free; + inherited; end; -function TPublishDiagnostics.HaveDiagnostics: Boolean; -begin - Result:=DiagnosticParams.diagnostics.Count>0; -end; - { TPublishDiagnosticsParams } procedure TPublishDiagnosticsParams.SetDiagnostics(AValue: TDiagnosticItems); diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 83e48dd..3f5ded5 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -25,10 +25,12 @@ interface uses { RTL } - Classes, Types, + Classes, Types, fgl, { Code Tools } - CodeToolManager, CodeCache, + CodeToolManager, CodeCache, CodeTree, CodeAtom, + BasicCodeTools, PascalReaderTool, PascalParserTool, { Protocol } + PasLS.CodeUtils, LSP.BaseTypes, LSP.Base, LSP.Basic, LSP.Window, LSP.Messages, LSP.Diagnostics; Type @@ -36,15 +38,38 @@ interface TDiagnosticsHandler = Class private - procedure AddCodeToolError(Diagnostics: TPublishDiagnostics; aTransport: TMessageTransport); - procedure AddUserDiagnostic(Diagnostics: TPublishDiagnostics; aTransport: TMessageTransport; UserMessage: String); - procedure ClearDiagnostics(aTransport: TMessageTransport; Code: TCodeBuffer); + fPublishDiagnostics: TPublishDiagnostics; + + procedure AddCodeToolError(aTransport: TMessageTransport); + procedure AddUserDiagnostic(aTransport: TMessageTransport; UserMessage: String); procedure ShowErrorMessage(aTransport: TMessageTransport; const MessageString: String); - function StrictSyntaxCheck(aDiagnostics : TPublishDiagnostics; aTransport: TMessageTransport; Code: TCodeBuffer): Boolean; - function CodeToolsCheckSyntax(aDiagnostics: TPublishDiagnostics; aTransport: TMessageTransport; Code: TCodeBuffer): boolean; + function StrictSyntaxCheck(aTransport: TMessageTransport; Code: TCodeBuffer): Boolean; + function CodeToolsCheckSyntax(aTransport: TMessageTransport; Code: TCodeBuffer): boolean; Public + constructor Create; + destructor Destroy; override; procedure CheckSyntax(aTransport : TMessageTransport; Code: TCodeBuffer); procedure SendDiagnosticMessage(aTransport : TMessageTransport; UserMessage: String = ''); + procedure AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); + end; + + TIdentifierGatherer = class + private + FIdentifiers: TCodeXYPositions; + {$if FPC_FULLVERSION >= 30301} + procedure OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean; + RefsStart: integer); + {$else} + procedure OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean); + {$endif} + + public + constructor Create(AIdentifiers: TCodeXYPositions); + procedure Gather(Tool: TPascalReaderTool); end; Function DiagnosticsHandler : TDiagnosticsHandler; @@ -73,13 +98,29 @@ procedure PublishCodeToolsError(aTransport: TMessageTransport; DiagnosticsHandler.SendDiagnosticMessage(aTransport,aMessage); end; -Procedure TDiagnosticsHandler.AddUserDiagnostic(Diagnostics : TPublishDiagnostics; aTransport: TMessageTransport; UserMessage : String); +constructor TDiagnosticsHandler.Create; +begin + inherited; + + fPublishDiagnostics := TPublishDiagnostics.Create; +end; + +destructor TDiagnosticsHandler.Destroy; +begin + fPublishDiagnostics.Free; + + inherited; +end; + +Procedure TDiagnosticsHandler.AddUserDiagnostic(aTransport: TMessageTransport; UserMessage : String); begin + // Clear previous user message on new message + fPublishDiagnostics.ClearUserMessages; // Message on stdErr aTransport.SendDiagnostic(UserMessage); // Actual diagnostic - Diagnostics.Add('', + fPublishDiagnostics.AddUserMessage( UserMessage, 0, 0, @@ -104,7 +145,7 @@ procedure PublishCodeToolsError(aTransport: TMessageTransport; end; -Procedure TDiagnosticsHandler.AddCodeToolError(Diagnostics : TPublishDiagnostics; aTransport: TMessageTransport); +Procedure TDiagnosticsHandler.AddCodeToolError(aTransport: TMessageTransport); Var MessageString : String; @@ -135,7 +176,7 @@ procedure PublishCodeToolsError(aTransport: TMessageTransport; if ServerSettings.showSyntaxErrors then ShowErrorMessage(aTransport, MessageString); if aFileName<>'' then - Diagnostics.Add(aFileName, + fPublishDiagnostics.AddCodeToolError(aFileName, aErrorMessage, aLine - 1, aCol - 1, @@ -148,22 +189,23 @@ procedure PublishCodeToolsError(aTransport: TMessageTransport; procedure TDiagnosticsHandler.SendDiagnosticMessage(aTransport : TMessageTransport; UserMessage: String = ''); var - Notification: TPublishDiagnostics; + fileName: string; begin - Notification:=TPublishDiagnostics.Create; - try - if UserMessage <> '' then - AddUserDiagnostic(Notification,aTransport,UserMessage) - else if (CodeToolBoss.ErrorCode<>Nil) then - AddCodeToolError(Notification,aTransport); - if not ServerSettings.publishDiagnostics then - exit; - if Notification.HaveDiagnostics then - Notification.Send(aTransport); - finally - Notification.Free; - end; + if UserMessage <> '' then + begin + AddUserDiagnostic(aTransport,UserMessage); + fileName := ''; + end + else + if (CodeToolBoss.ErrorCode<>Nil) then + begin + AddCodeToolError(aTransport); + fileName:=CodeToolBoss.ErrorCode.FileName; + end; + if not ServerSettings.publishDiagnostics then + exit; + fPublishDiagnostics.SendDiagnostics(fileName, aTransport); end; Type @@ -175,24 +217,22 @@ TErrorReporter = class FErrorCount: Integer; FHandler : TDiagnosticsHandler; FParser : TSourceParser; - FDiagnostics : TPublishDiagnostics; FTransport : TMessageTransport; Protected procedure ReportError(Sender: TObject; const aError, aFileName: string; aCode, aLine, aCol: Integer); Public - Constructor Create(aHandler : TDiagnosticsHandler; aParser : TSourceParser;aDiagnostics : TPublishDiagnostics; aTransport : TMessageTransport); + Constructor Create(aHandler : TDiagnosticsHandler; aParser : TSourceParser; aTransport : TMessageTransport); Property ErrorCount : Integer Read FErrorCount; end; { TErrorReporter } constructor TErrorReporter.Create(aHandler: TDiagnosticsHandler; - aParser: TSourceParser; aDiagnostics: TPublishDiagnostics; + aParser: TSourceParser; aTransport: TMessageTransport); begin FHandler:=aHandler; FParser:=aParser; - FDiagnostics:=aDiagnostics; FTransport:=aTransport; FParser.OnError:=@ReportError; end; @@ -210,7 +250,7 @@ procedure TErrorReporter.ReportError(Sender: TObject; const aError, if ServerSettings.showSyntaxErrors then FHandler.ShowErrorMessage(FTransport,S); if ServerSettings.publishDiagnostics then - FDiagnostics.Add(aFileName, + FHandler.AddParserError(aFileName, aError, aLine-1, aCol-1, @@ -218,7 +258,7 @@ procedure TErrorReporter.ReportError(Sender: TObject; const aError, TDiagnosticSeverity.Error); end; -function TDiagnosticsHandler.StrictSyntaxCheck(aDiagnostics : TPublishDiagnostics; aTransport : TMessageTransport; Code: TCodeBuffer) : Boolean; +function TDiagnosticsHandler.StrictSyntaxCheck(aTransport : TMessageTransport; Code: TCodeBuffer) : Boolean; Var Module : TPasModule; @@ -245,7 +285,7 @@ function TDiagnosticsHandler.StrictSyntaxCheck(aDiagnostics : TPublishDiagnostic Args[i]:=ServerSettings.fpcOptions[i]; Args[Length(Args)-1]:=Code.Filename; SourceParser.CommandLine:=Args; - Reporter:=TErrorReporter.Create(Self,SourceParser,aDiagnostics,aTransport); + Reporter:=TErrorReporter.Create(Self,SourceParser,aTransport); Module:=SourceParser.ParseSource; Result:=Reporter.ErrorCount=0; except @@ -262,60 +302,113 @@ function TDiagnosticsHandler.StrictSyntaxCheck(aDiagnostics : TPublishDiagnostic procedure TDiagnosticsHandler.CheckSyntax(aTransport : TMessageTransport; Code: TCodeBuffer); Var - Diagnostics : TPublishDiagnostics; CodeOK : Boolean; begin if not ServerSettings.checkSyntax then exit; - // All diagnostics in 1 message. - Diagnostics := TPublishDiagnostics.Create; - try - // Check code. These routines will possibly send messages to a window or stdout, depending on settings. - CodeOk:=CodeToolsCheckSyntax(Diagnostics,aTransport,Code); - if CodeOK then - CodeOK:=StrictSyntaxCheck(Diagnostics,aTransport,Code); - // If we need to publish settings, then send the diagnostics. - if ServerSettings.publishDiagnostics then - begin - if CodeOK then - Diagnostics.Clear(Code.FileName); - Diagnostics.Send(aTransport); - end; - finally - Diagnostics.Free; - end; + // Check code. These routines will possibly send messages to a window or stdout, depending on settings. + fPublishDiagnostics.ClearCodeToolErrors(Code.Filename); + fPublishDiagnostics.ClearParserError(Code.Filename); + + CodeOk:=CodeToolsCheckSyntax(aTransport,Code); + if CodeOK then + CodeOK:=StrictSyntaxCheck(aTransport,Code); + // If we need to publish settings, then send the diagnostics. + if ServerSettings.publishDiagnostics then + fPublishDiagnostics.SendDiagnostics(Code.Filename, aTransport); end; -function TDiagnosticsHandler.CodeToolsCheckSyntax(aDiagnostics: TPublishDiagnostics; aTransport : TMessageTransport; Code: TCodeBuffer): boolean; +function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport; Code: TCodeBuffer): boolean; var Tool: TCodeTool; + Node: TCodeTreeNode; + IdentifiersPos: TCodeXYPositions; + Gatherer: TIdentifierGatherer; + NewCode: TCodeBuffer; + NewX, NewY, NewTopLine: integer; + i: Integer; begin // Check for errors. Result:=CodeToolBoss.Explore(Code,Tool,true); if not Result then - // Errors found ? Publish them. - AddCodeToolError(aDiagnostics,aTransport); -end; + // Errors found ? Publish them. + AddCodeToolError(aTransport); -procedure TDiagnosticsHandler.ClearDiagnostics(aTransport : TMessageTransport; Code: TCodeBuffer); -var - Diagnostics: TPublishDiagnostics; -begin - if not ServerSettings.publishDiagnostics then - Exit; - Diagnostics:=TPublishDiagnostics.Create; try - Diagnostics.Clear(Code.FileName); - Diagnostics.Send(aTransport); + IdentifiersPos := TCodeXYPositions.Create; + Gatherer := TIdentifierGatherer.Create(IdentifiersPos); + Gatherer.Gather(Tool); + + for i := 0 to IdentifiersPos.Count - 1 do + begin + with IdentifiersPos.Items[i]^ do + begin + if CodeToolBoss.FindMainDeclaration(Code, X, Y, NewCode, NewX, NewY, NewTopLine) then + Continue + else + AddCodeToolError(aTransport); + end; + end finally - Diagnostics.Free; + Gatherer.Free; + IdentifiersPos.Free; end; end; +procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); +begin + fPublishDiagnostics.AddParserError(fileName, message, line, column, code, severity); +end; + +constructor TIdentifierGatherer.Create(AIdentifiers: TCodeXYPositions); +begin + FIdentifiers := AIdentifiers; +end; + +{$if FPC_FULLVERSION >= 30301} +procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean; + RefsStart: integer); +{$else} +procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean); +{$endif} + + +var + IdentifierStr: string; + CodeTool: TCodeTool; + IdentifierPos: TCodeXYPosition; + NewTopLine: Integer; +begin + if not (Sender is TCodeTool) then + Exit; + + CodeTool := TCodeTool(Sender); + if CodeTool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then + begin + IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); + if IdentifierStr <> '' then + begin + FIdentifiers.Add(IdentifierPos); + end; + end; +end; + +procedure TIdentifierGatherer.Gather(Tool: TPascalReaderTool); +begin +{$if FPC_FULLVERSION >= 30301} + Tool.ForEachIdentifier(true, @OnIdentifierFound, nil, 0); +{$else} + Tool.ForEachIdentifier(true, @OnIdentifierFound, nil); +{$endif} +end; Initialization