From e9e168ebaf9b75091ae1fedee27f513f6c30a360 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 21:10:58 +0700 Subject: [PATCH 01/15] Add TUriDiagnostics type for URI-based diagnostics mapping --- src/protocol/LSP.Basic.pas | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/protocol/LSP.Basic.pas b/src/protocol/LSP.Basic.pas index c36c0b3..acf817c 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 @@ -410,6 +410,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 From 814cd6cd2a95cbc6fbdf98ebad63b89896fa3bcf Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 21:35:54 +0700 Subject: [PATCH 02/15] Centralize TDiagnosticItes and TUriDiagnostics in TPublishDiagnostics - create fUserMessages, fCodeToolErrors, fParserErrors to hold diagnostic param items - Create infrastructure methods to add/clear into theese fields --- src/protocol/LSP.Diagnostics.pas | 193 +++++++++++++++++++++++++++++-- 1 file changed, 186 insertions(+), 7 deletions(-) diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index 8c5441f..5369270 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,113 @@ 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; +begin + DiagnosticParams.uri := PathToURI(fileName); + if not fCodeToolErrors. + TryGetData(DiagnosticParams.uri, CodeToolErrorsDiagnostics) + then + begin + CodeToolErrorsDiagnostics := TDiagnosticItems.Create; + fCodeToolErrors.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.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 +213,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 +232,67 @@ function TPublishDiagnostics.GetDiagnosticParams: TPublishDiagnosticsParams; Result:=Params as TPublishDiagnosticsParams; end; +procedure TPublishDiagnostics.SendDiagnostics( + fileName: string; + aTransport: TMessageTransport + ); +var + Diagnostic, sentDiagnostic: TDiagnostic; + IsHaveDiagnostics: Boolean; + + 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 + if not IsHaveDiagnostics then + begin + IsHaveDiagnostics := True; + Clear(fileName); + end; + sentDiagnostic := DiagnosticParams.diagnostics.Add; + sentDiagnostic.Assign(Diagnostic); + end; + end; +begin + IsHaveDiagnostics := False; + DiagnosticParams.diagnostics.Clear; + // loop over all fCodeToolErrors[fileName] and fParserErrors[fileName] + // add to DiagnosticParams.diagnostics + IterateDiagnosticItems(fCodeToolErrors); + IterateDiagnosticItems(fParserErrors); + + // if fUserMessages.count > 0 add to DiagnosticParams.diagnostics + if Length(fileName) = 0 then + for TCollectionItem(Diagnostic) in fUserMessages do + begin + if notIsHaveDiagnostics then + begin + IsHaveDiagnostics := True; + Clear(fileName); + end; + sentDiagnostic := DiagnosticParams.diagnostics.Add; + sentDiagnostic.Assign(Diagnostic); + 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 +300,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); From b70ff0df240fb09be959a290aee2c19953561de1 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 21:46:56 +0700 Subject: [PATCH 03/15] Refactor diagnostics handling to use persistent handler state --- src/serverprotocol/PasLS.Diagnostics.pas | 124 +++++++++++------------ 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 83e48dd..b6b18ae 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -36,15 +36,19 @@ 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; Function DiagnosticsHandler : TDiagnosticsHandler; @@ -73,13 +77,27 @@ 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 // Message on stdErr aTransport.SendDiagnostic(UserMessage); // Actual diagnostic - Diagnostics.Add('', + fPublishDiagnostics.AddUserMessage( UserMessage, 0, 0, @@ -104,7 +122,7 @@ procedure PublishCodeToolsError(aTransport: TMessageTransport; end; -Procedure TDiagnosticsHandler.AddCodeToolError(Diagnostics : TPublishDiagnostics; aTransport: TMessageTransport); +Procedure TDiagnosticsHandler.AddCodeToolError(aTransport: TMessageTransport); Var MessageString : String; @@ -135,7 +153,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 +166,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 +194,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 +227,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 +235,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; @@ -228,6 +245,8 @@ function TDiagnosticsHandler.StrictSyntaxCheck(aDiagnostics : TPublishDiagnostic Reporter : TErrorReporter; begin + fPublishDiagnostics.ClearParserError(Code.FileName); + Args:=[]; Result:=False; Module:=nil; @@ -245,7 +264,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,32 +281,22 @@ 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); + 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; @@ -298,25 +307,14 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aDiagnostics: TPublishDiagnost if not Result then // Errors found ? Publish them. - AddCodeToolError(aDiagnostics,aTransport); + AddCodeToolError(aTransport); end; -procedure TDiagnosticsHandler.ClearDiagnostics(aTransport : TMessageTransport; Code: TCodeBuffer); -var - Diagnostics: TPublishDiagnostics; +procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); begin - if not ServerSettings.publishDiagnostics then - Exit; - Diagnostics:=TPublishDiagnostics.Create; - try - Diagnostics.Clear(Code.FileName); - Diagnostics.Send(aTransport); - finally - Diagnostics.Free; - end; + fPublishDiagnostics.AddParserError(fileName, message, line, column, code, severity); end; - Initialization Finalization From 5666c572ab8836db9e6807e392a70a9db41a9a66 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 21:48:03 +0700 Subject: [PATCH 04/15] Fix typo in diagnostic check condition --- src/protocol/LSP.Diagnostics.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index 5369270..772a2e8 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -275,7 +275,7 @@ procedure TPublishDiagnostics.SendDiagnostics( if Length(fileName) = 0 then for TCollectionItem(Diagnostic) in fUserMessages do begin - if notIsHaveDiagnostics then + if not IsHaveDiagnostics then begin IsHaveDiagnostics := True; Clear(fileName); From 8be237f27d301a88abb08967601c787f60f58bad Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 22:01:48 +0700 Subject: [PATCH 05/15] Remove trailing whitespace in LSP.Diagnostics.pas --- src/protocol/LSP.Diagnostics.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index 772a2e8..f3d6d44 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -85,7 +85,7 @@ TPublishDiagnostics = class(TNotificationMessage) procedure Add(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); procedure ClearCodeToolErrors(fileName: string); procedure ClearParserError(fileName: string); - procedure ClearUserMessages; + procedure ClearUserMessages; procedure Clear(fileName: string); end; From 71aa1cf53a8ee9f0d3c6adc270378d5f86e066ff Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 5 Sep 2026 22:01:59 +0700 Subject: [PATCH 06/15] Clear previous user messages on new user diagnostic --- src/serverprotocol/PasLS.Diagnostics.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index b6b18ae..bf53054 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -94,6 +94,8 @@ destructor TDiagnosticsHandler.Destroy; 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 From 8f36b18eb43b35e54bb316b01eace86ecb03f468 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 9 Sep 2026 13:52:21 +0700 Subject: [PATCH 07/15] Add range checking and improve diagnostics handling - Only add CodeTool error if there are no error previously on that location - Improve CodeToolCheckSyntax to scan unknown identifier --- src/protocol/LSP.Basic.pas | 12 +++ src/protocol/LSP.Diagnostics.pas | 16 +++- src/serverprotocol/PasLS.Diagnostics.pas | 98 ++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/protocol/LSP.Basic.pas b/src/protocol/LSP.Basic.pas index acf817c..cb7f3f0 100644 --- a/src/protocol/LSP.Basic.pas +++ b/src/protocol/LSP.Basic.pas @@ -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; @@ -977,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); diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index f3d6d44..c72ca8e 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -159,6 +159,7 @@ procedure TPublishDiagnostics.AddCodeToolError( var CodeToolErrorsDiagnostics: TDiagnosticItems; Diagnostic: TDiagnostic; + i: Integer; begin DiagnosticParams.uri := PathToURI(fileName); if not fCodeToolErrors. @@ -169,7 +170,20 @@ procedure TPublishDiagnostics.AddCodeToolError( fCodeToolErrors.Add(DiagnosticParams.uri, CodeToolErrorsDiagnostics); end; - Diagnostic := CodeToolErrorsDiagnostics.Add; + 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; diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index bf53054..3a791e9 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -27,7 +27,7 @@ interface { RTL } Classes, Types, { Code Tools } - CodeToolManager, CodeCache, + CodeToolManager, CodeCache, CodeTree, CodeAtom, { Protocol } LSP.BaseTypes, LSP.Base, LSP.Basic, LSP.Window, LSP.Messages, LSP.Diagnostics; @@ -247,8 +247,6 @@ function TDiagnosticsHandler.StrictSyntaxCheck(aTransport : TMessageTransport; C Reporter : TErrorReporter; begin - fPublishDiagnostics.ClearParserError(Code.FileName); - Args:=[]; Result:=False; Module:=nil; @@ -290,6 +288,8 @@ procedure TDiagnosticsHandler.CheckSyntax(aTransport : TMessageTransport; Code: exit; // 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); @@ -302,14 +302,102 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport var Tool: TCodeTool; + Node: TCodeTreeNode; + Identifier: string; + CursorPos: TCodeXYPosition; + NewCode: TCodeBuffer; + NewX, NewY, NewTopLine, BlockTopLine, BlockBottomLine: Integer; + + + function IsIdentifier(CodeBuffer: TCodeBuffer; X, Y: Integer): Boolean; + var + IsString, IsComment, isKeyword: Boolean; + CursorPos: TCodeXYPosition; + CodeTool: TCodeTool; + SameArea: TAtomPosition; + CleanPos: integer; + begin + IsString := False; + IsComment := False; + isKeyword := False; + + CursorPos.Code := CodeBuffer; + CursorPos.X := X; + CursorPos.Y := Y; + CodeTool:=TCodeTool(CodeToolBoss.FindCodeToolForSource(CodeBuffer)); + + if CodeTool.CaretToCleanPos(CursorPos, CleanPos) <> 0 then + exit; + + CodeTool.BuildTreeAndGetCleanPos(CursorPos, CleanPos); + CodeTool.GetCleanPosInfo(-1, CleanPos, false, SameArea); + + if SameArea.Flag = cafNone then + IsComment := (SameArea.StartPos <= CleanPos) and (CleanPos < SameArea.EndPos); + + if not IsComment then + begin + CodeTool.MoveCursorToCleanPos(SameArea.StartPos); + CodeTool.ReadNextAtom; + + if CodeTool.AtomIsStringConstant then + IsString := True + else if CodeTool.StringIsKeyWord(CodeTool.GetAtom) then + isKeyword := True; + end; + + Result := not (IsString or isKeyword or IsComment); + end; begin // Check for errors. Result:=CodeToolBoss.Explore(Code,Tool,true); if not Result then - // Errors found ? Publish them. - AddCodeToolError(aTransport); + begin + // Errors found ? Publish them. + AddCodeToolError(aTransport); + Exit; + end; + + Node := Tool.Tree.Root; + while Node <> nil do + begin + if Node.Desc = ctnIdentifier then + begin + Identifier := Tool.GetNodeIdentifier(Node); + Tool.CleanPosToCaret(Node.StartPos, CursorPos); + if IsIdentifier(Code, CursorPos.X, CursorPos.Y) then + begin + if not CodeToolBoss.FindDeclaration( + Code, + CursorPos.X, + CursorPos.Y, + NewCode, + NewX, + NewY, + NewTopLine, + BlockTopLine, + BlockBottomLine + ) then + begin + AddCodeToolError(aTransport); + end; + end; + end; + + if Node.FirstChild <> nil then + Node := Node.FirstChild + else if Node.NextBrother <> nil then + Node := Node.NextBrother + else + begin + while (Node <> nil) and (Node.NextBrother = nil) do + Node := Node.Parent; + if Node <> nil then + Node := Node.NextBrother; + end; + end; end; procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); From 1ce98b0fa779e8f64ab53f1de7e3afa28dbcdcc0 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 09:33:26 +0700 Subject: [PATCH 08/15] Refactor identifier gathering into TIdentifierGatherer class --- src/serverprotocol/PasLS.Diagnostics.pas | 133 +++++++++-------------- 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 3a791e9..5800ecb 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -27,7 +27,8 @@ interface { RTL } Classes, Types, { Code Tools } - CodeToolManager, CodeCache, CodeTree, CodeAtom, + CodeToolManager, CodeCache, CodeTree, CodeAtom, + BasicCodeTools, PascalReaderTool, PascalParserTool, { Protocol } LSP.BaseTypes, LSP.Base, LSP.Basic, LSP.Window, LSP.Messages, LSP.Diagnostics; @@ -51,6 +52,18 @@ interface procedure AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); end; + TIdentifierGatherer = class + private + FIdentifiers: TStrings; + procedure OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean; + RefsStart: integer); + public + constructor Create(AIdentifiers: TStrings); + procedure Gather(Tool: TPascalReaderTool); + end; + Function DiagnosticsHandler : TDiagnosticsHandler; procedure PublishCodeToolsError(aTransport : TMessageTransport; const aMessage : string); @@ -303,51 +316,8 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport var Tool: TCodeTool; Node: TCodeTreeNode; - Identifier: string; - CursorPos: TCodeXYPosition; - NewCode: TCodeBuffer; - NewX, NewY, NewTopLine, BlockTopLine, BlockBottomLine: Integer; - - - function IsIdentifier(CodeBuffer: TCodeBuffer; X, Y: Integer): Boolean; - var - IsString, IsComment, isKeyword: Boolean; - CursorPos: TCodeXYPosition; - CodeTool: TCodeTool; - SameArea: TAtomPosition; - CleanPos: integer; - begin - IsString := False; - IsComment := False; - isKeyword := False; - - CursorPos.Code := CodeBuffer; - CursorPos.X := X; - CursorPos.Y := Y; - CodeTool:=TCodeTool(CodeToolBoss.FindCodeToolForSource(CodeBuffer)); - - if CodeTool.CaretToCleanPos(CursorPos, CleanPos) <> 0 then - exit; - - CodeTool.BuildTreeAndGetCleanPos(CursorPos, CleanPos); - CodeTool.GetCleanPosInfo(-1, CleanPos, false, SameArea); - - if SameArea.Flag = cafNone then - IsComment := (SameArea.StartPos <= CleanPos) and (CleanPos < SameArea.EndPos); - - if not IsComment then - begin - CodeTool.MoveCursorToCleanPos(SameArea.StartPos); - CodeTool.ReadNextAtom; - - if CodeTool.AtomIsStringConstant then - IsString := True - else if CodeTool.StringIsKeyWord(CodeTool.GetAtom) then - isKeyword := True; - end; - - Result := not (IsString or isKeyword or IsComment); - end; + Identifiers: TStringList; + Gatherer: TIdentifierGatherer; begin // Check for errors. @@ -360,44 +330,17 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport Exit; end; - Node := Tool.Tree.Root; - while Node <> nil do - begin - if Node.Desc = ctnIdentifier then - begin - Identifier := Tool.GetNodeIdentifier(Node); - Tool.CleanPosToCaret(Node.StartPos, CursorPos); - if IsIdentifier(Code, CursorPos.X, CursorPos.Y) then - begin - if not CodeToolBoss.FindDeclaration( - Code, - CursorPos.X, - CursorPos.Y, - NewCode, - NewX, - NewY, - NewTopLine, - BlockTopLine, - BlockBottomLine - ) then - begin - AddCodeToolError(aTransport); - end; - end; - end; - - if Node.FirstChild <> nil then - Node := Node.FirstChild - else if Node.NextBrother <> nil then - Node := Node.NextBrother - else - begin - while (Node <> nil) and (Node.NextBrother = nil) do - Node := Node.Parent; - if Node <> nil then - Node := Node.NextBrother; - end; - end; + try + Identifiers := TStringList.Create; + Gatherer := TIdentifierGatherer.Create(Identifiers); + Gatherer.Gather(Tool); + + Identifiers.Delimiter := ','; + aTransport.SendDiagnostic('===theoi: %s', [Identifiers.DelimitedText]); + finally + Gatherer.Free; + Identifiers.Free; + end; end; procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, column, code: integer; severity: TDiagnosticSeverity); @@ -405,6 +348,28 @@ procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, co fPublishDiagnostics.AddParserError(fileName, message, line, column, code, severity); end; +constructor TIdentifierGatherer.Create(AIdentifiers: TStrings); +begin + FIdentifiers := AIdentifiers; +end; + +procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; + IdentifierCleanPos: integer; Range: TEPRIRange; + Node: TCodeTreeNode; Data: Pointer; var Abort: boolean; + RefsStart: integer); +var + IdentifierStr: string; +begin + IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); + if IdentifierStr <> '' then + FIdentifiers.Add(IdentifierStr); +end; + +procedure TIdentifierGatherer.Gather(Tool: TPascalReaderTool); +begin + Tool.ForEachIdentifier(true, @OnIdentifierFound, nil, 0); +end; + Initialization Finalization From 0dc4a074173192f42d288d727bbb4cc43c3699c9 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 14:34:21 +0700 Subject: [PATCH 09/15] Filter out declared identifiers in TIdentifierGatherer --- src/serverprotocol/PasLS.Diagnostics.pas | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 5800ecb..85bb9d1 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -359,10 +359,30 @@ procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; RefsStart: integer); var IdentifierStr: string; + codeTool: TCodeTool; + IdentifierPos, NewPos: TCodeXYPosition; + NewTopLine: Integer; begin IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); if IdentifierStr <> '' then - FIdentifiers.Add(IdentifierStr); + begin + codeTool := TCodeTool(Sender); + codeTool.MoveCursorToCleanPos(IdentifierCleanPos); + codeTool.ReadNextAtom; + if not (codeTool.AtomIsStringConstant or codeTool.StringIsKeyWord(codeTool.GetAtom)) and + codetool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then + begin + try + if not codeTool.FindMainDeclaration(IdentifierPos,NewPos,NewTopLine) then + begin + FIdentifiers.Add(IdentifierStr); + end; + except + on e: Exception do + FIdentifiers.Add(IdentifierStr); + end; + end; + end; end; procedure TIdentifierGatherer.Gather(Tool: TPascalReaderTool); From 80648c91a1d87159e29957d2d6259efbd88977fb Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 15:10:30 +0700 Subject: [PATCH 10/15] Use TCodeXYPositions for identifier gathering in diagnostics --- src/serverprotocol/PasLS.Diagnostics.pas | 60 +++++++++++++----------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 85bb9d1..32c3358 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -25,11 +25,12 @@ interface uses { RTL } - Classes, Types, + Classes, Types, fgl, { Code Tools } CodeToolManager, CodeCache, CodeTree, CodeAtom, BasicCodeTools, PascalReaderTool, PascalParserTool, { Protocol } + PasLS.CodeUtils, LSP.BaseTypes, LSP.Base, LSP.Basic, LSP.Window, LSP.Messages, LSP.Diagnostics; Type @@ -54,13 +55,13 @@ interface TIdentifierGatherer = class private - FIdentifiers: TStrings; + FIdentifiers: TCodeXYPositions; procedure OnIdentifierFound(Sender: TPascalParserTool; IdentifierCleanPos: integer; Range: TEPRIRange; Node: TCodeTreeNode; Data: Pointer; var Abort: boolean; RefsStart: integer); public - constructor Create(AIdentifiers: TStrings); + constructor Create(AIdentifiers: TCodeXYPositions); procedure Gather(Tool: TPascalReaderTool); end; @@ -316,8 +317,11 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport var Tool: TCodeTool; Node: TCodeTreeNode; - Identifiers: TStringList; + IdentifiersPos: TCodeXYPositions; Gatherer: TIdentifierGatherer; + NewCode: TCodeBuffer; + NewX, NewY, NewTopLine: integer; + i: Integer; begin // Check for errors. @@ -331,15 +335,23 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport end; try - Identifiers := TStringList.Create; - Gatherer := TIdentifierGatherer.Create(Identifiers); + IdentifiersPos := TCodeXYPositions.Create; + Gatherer := TIdentifierGatherer.Create(IdentifiersPos); Gatherer.Gather(Tool); - Identifiers.Delimiter := ','; - aTransport.SendDiagnostic('===theoi: %s', [Identifiers.DelimitedText]); + 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 Gatherer.Free; - Identifiers.Free; + IdentifiersPos.Free; end; end; @@ -348,7 +360,7 @@ procedure TDiagnosticsHandler.AddParserError(fileName, message: string; line, co fPublishDiagnostics.AddParserError(fileName, message, line, column, code, severity); end; -constructor TIdentifierGatherer.Create(AIdentifiers: TStrings); +constructor TIdentifierGatherer.Create(AIdentifiers: TCodeXYPositions); begin FIdentifiers := AIdentifiers; end; @@ -359,28 +371,20 @@ procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; RefsStart: integer); var IdentifierStr: string; - codeTool: TCodeTool; - IdentifierPos, NewPos: TCodeXYPosition; + CodeTool: TCodeTool; + IdentifierPos: TCodeXYPosition; NewTopLine: Integer; begin - IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); - if IdentifierStr <> '' then + if not (Sender is TCodeTool) then + Exit; + + CodeTool := TCodeTool(Sender); + if CodeTool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then begin - codeTool := TCodeTool(Sender); - codeTool.MoveCursorToCleanPos(IdentifierCleanPos); - codeTool.ReadNextAtom; - if not (codeTool.AtomIsStringConstant or codeTool.StringIsKeyWord(codeTool.GetAtom)) and - codetool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then + IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); + if IdentifierStr <> '' then begin - try - if not codeTool.FindMainDeclaration(IdentifierPos,NewPos,NewTopLine) then - begin - FIdentifiers.Add(IdentifierStr); - end; - except - on e: Exception do - FIdentifiers.Add(IdentifierStr); - end; + FIdentifiers.Add(IdentifierPos); end; end; end; From 339b32ebd0255dd8aebaae9e9e2dd746d75a986b Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 15:14:23 +0700 Subject: [PATCH 11/15] Change diagnostic message type to TOptionalString --- src/protocol/LSP.Basic.pas | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/protocol/LSP.Basic.pas b/src/protocol/LSP.Basic.pas index cb7f3f0..06ae990 100644 --- a/src/protocol/LSP.Basic.pas +++ b/src/protocol/LSP.Basic.pas @@ -379,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; @@ -397,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 @@ -1119,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); From 076a8664eef567be9f134769922d7bd052ebd594 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 16:19:10 +0700 Subject: [PATCH 12/15] Add FPC version compatibility for identifier gathering --- src/serverprotocol/PasLS.Diagnostics.pas | 28 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 32c3358..72b695f 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -56,10 +56,17 @@ interface 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); @@ -328,14 +335,11 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport Result:=CodeToolBoss.Explore(Code,Tool,true); if not Result then - begin // Errors found ? Publish them. AddCodeToolError(aTransport); - Exit; - end; try - IdentifiersPos := TCodeXYPositions.Create; + IdentifiersPos := TCodeXYPositions.Create Gatherer := TIdentifierGatherer.Create(IdentifiersPos); Gatherer.Gather(Tool); @@ -364,11 +368,19 @@ 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; @@ -378,7 +390,7 @@ procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; if not (Sender is TCodeTool) then Exit; - CodeTool := TCodeTool(Sender); + CodeTool := TCodeTool(Sender) if CodeTool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then begin IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); @@ -391,7 +403,11 @@ procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; 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 From 2f69f550b945040094bafa20c198ed8a90819859 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 10 Sep 2026 22:27:58 +0700 Subject: [PATCH 13/15] Fix missing semicolons in PasLS.Diagnostics --- src/serverprotocol/PasLS.Diagnostics.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serverprotocol/PasLS.Diagnostics.pas b/src/serverprotocol/PasLS.Diagnostics.pas index 72b695f..3f5ded5 100644 --- a/src/serverprotocol/PasLS.Diagnostics.pas +++ b/src/serverprotocol/PasLS.Diagnostics.pas @@ -339,7 +339,7 @@ function TDiagnosticsHandler.CodeToolsCheckSyntax(aTransport : TMessageTransport AddCodeToolError(aTransport); try - IdentifiersPos := TCodeXYPositions.Create + IdentifiersPos := TCodeXYPositions.Create; Gatherer := TIdentifierGatherer.Create(IdentifiersPos); Gatherer.Gather(Tool); @@ -390,7 +390,7 @@ procedure TIdentifierGatherer.OnIdentifierFound(Sender: TPascalParserTool; if not (Sender is TCodeTool) then Exit; - CodeTool := TCodeTool(Sender) + CodeTool := TCodeTool(Sender); if CodeTool.CleanPosToCaretAndTopLine(IdentifierCleanPos, IdentifierPos, NewTopLine) then begin IdentifierStr := GetIdentifier(@Sender.Src[IdentifierCleanPos]); From acaa9bc84c1ef0ad6788f3bb2aff6de3645238a4 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 12 Sep 2026 05:39:38 +0700 Subject: [PATCH 14/15] Simplify sendDiagnostic conditions --- src/protocol/LSP.Diagnostics.pas | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index c72ca8e..08a1c36 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -252,7 +252,6 @@ procedure TPublishDiagnostics.SendDiagnostics( ); var Diagnostic, sentDiagnostic: TDiagnostic; - IsHaveDiagnostics: Boolean; procedure IterateDiagnosticItems(uriDiagnostics: TUriDiagnostics); var @@ -268,17 +267,11 @@ procedure TPublishDiagnostics.SendDiagnostics( for TCollectionItem(Diagnostic) in DiagnosticItems do begin - if not IsHaveDiagnostics then - begin - IsHaveDiagnostics := True; - Clear(fileName); - end; sentDiagnostic := DiagnosticParams.diagnostics.Add; sentDiagnostic.Assign(Diagnostic); end; end; begin - IsHaveDiagnostics := False; DiagnosticParams.diagnostics.Clear; // loop over all fCodeToolErrors[fileName] and fParserErrors[fileName] // add to DiagnosticParams.diagnostics @@ -289,11 +282,6 @@ procedure TPublishDiagnostics.SendDiagnostics( if Length(fileName) = 0 then for TCollectionItem(Diagnostic) in fUserMessages do begin - if not IsHaveDiagnostics then - begin - IsHaveDiagnostics := True; - Clear(fileName); - end; sentDiagnostic := DiagnosticParams.diagnostics.Add; sentDiagnostic.Assign(Diagnostic); end; From 793e7a0bf71da70fd158eb7f5ef15eb45dd60b82 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 12 Sep 2026 06:00:54 +0700 Subject: [PATCH 15/15] Fix diagnostic URI assignment --- src/protocol/LSP.Diagnostics.pas | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/protocol/LSP.Diagnostics.pas b/src/protocol/LSP.Diagnostics.pas index 08a1c36..0e9b423 100644 --- a/src/protocol/LSP.Diagnostics.pas +++ b/src/protocol/LSP.Diagnostics.pas @@ -271,20 +271,28 @@ procedure TPublishDiagnostics.SendDiagnostics( sentDiagnostic.Assign(Diagnostic); end; end; + begin DiagnosticParams.diagnostics.Clear; - // loop over all fCodeToolErrors[fileName] and fParserErrors[fileName] - // add to DiagnosticParams.diagnostics - IterateDiagnosticItems(fCodeToolErrors); - IterateDiagnosticItems(fParserErrors); - // if fUserMessages.count > 0 add to DiagnosticParams.diagnostics if Length(fileName) = 0 then - for TCollectionItem(Diagnostic) in fUserMessages do - begin - sentDiagnostic := DiagnosticParams.diagnostics.Add; - sentDiagnostic.Assign(Diagnostic); - end; + 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;