Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e9e168e
Add TUriDiagnostics type for URI-based diagnostics mapping
oisanjaya Sep 5, 2026
814cd6c
Centralize TDiagnosticItes and TUriDiagnostics in TPublishDiagnostics
oisanjaya Sep 5, 2026
b70ff0d
Refactor diagnostics handling to use persistent handler state
oisanjaya Sep 5, 2026
5666c57
Fix typo in diagnostic check condition
oisanjaya Sep 5, 2026
8be237f
Remove trailing whitespace in LSP.Diagnostics.pas
oisanjaya Sep 5, 2026
71aa1cf
Clear previous user messages on new user diagnostic
oisanjaya Sep 5, 2026
8f36b18
Add range checking and improve diagnostics handling
Sep 9, 2026
1ce98b0
Refactor identifier gathering into TIdentifierGatherer class
oisanjaya Sep 10, 2026
0dc4a07
Filter out declared identifiers in TIdentifierGatherer
Sep 10, 2026
80648c9
Use TCodeXYPositions for identifier gathering in diagnostics
oisanjaya Sep 10, 2026
339b32e
Change diagnostic message type to TOptionalString
oisanjaya Sep 10, 2026
076a866
Add FPC version compatibility for identifier gathering
Sep 10, 2026
2f69f55
Fix missing semicolons in PasLS.Diagnostics
oisanjaya Sep 10, 2026
acaa9bc
Simplify sendDiagnostic conditions
oisanjaya Sep 11, 2026
793e7a0
Fix diagnostic URI assignment
oisanjaya Sep 11, 2026
2f02ba9
Update TRange.InRange to support whole line checks
oisanjaya Oct 18, 2026
35bc136
Add range support to diagnostics and syntax checking
oisanjaya Oct 18, 2026
0f0381e
Enable incremental text document synchronization
oisanjaya Oct 16, 2026
26edff7
Add precise error ranges for CodeTool diagnostics
oisanjaya Oct 11, 2026
0cf5116
Add precise error ranges for CodeTool diagnostics
oisanjaya Oct 11, 2026
3499e3e
Merge branch 'diag_overhaul' into diag_overhaul-partial_didchange
oisanjaya Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/protocol/LSP.Basic.pas
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

interface
uses
FPJson,
FPJson, fgl,
Classes, SysUtils, LSP.BaseTypes, LSP.Messages;

type
Expand Down Expand Up @@ -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; WholeLine: Boolean = False): Boolean;
Destructor destroy; override;
Procedure Assign(Source : TPersistent); override;
function ToString: String; override;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -410,6 +411,7 @@ TDiagnostic = class (TCollectionItem)
end;

TDiagnosticItems = specialize TGenericCollection<TDiagnostic>;
TUriDiagnostics = specialize TFPGMapObject<string, TDiagnosticItems>;

{ TCommand
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command
Expand Down Expand Up @@ -976,6 +978,17 @@ procedure TRange.SetRange(startLine, startColumn: integer; endLine,
fEnd.Character:=endColumn;
end;

function TRange.InRange(line, column: integer; WholeLine: Boolean = False): Boolean;
begin
Result := ((fStart.line < line) and (fEnd.line > line)) or
((fStart.line = line) and (WholeLine or (fStart.character <= column)) and
((fEnd.line > line) or (WholeLine or (fEnd.character >= column)))
) or
((fEnd.line = line) and (WholeLine or (fEnd.character >= column)) and
((fStart.line < line) or (WholeLine or (fStart.character <= column)))
);
end;

destructor TRange.destroy;
begin
FreeAndNil(fStart);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/LSP.Capabilities.pas
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ constructor TServerCapabilities.Create;
fcompletionProvider := TCompletionOptions.Create;
fsignatureHelpProvider := TSignatureHelpOptions.Create;
fexecuteCommandProvider := TExecuteCommandOptions.Create;
ftextDocumentSync.change := TTextDocumentSyncKind.Full;
ftextDocumentSync.change := TTextDocumentSyncKind.Incremental;
fRenameProvider := TRenameOptions.Create;
end;

Expand Down
213 changes: 206 additions & 7 deletions src/protocol/LSP.Diagnostics.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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, eline, ecolumn, 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; Range: TRange = nil);
procedure ClearParserError(fileName: string);
procedure ClearUserMessages;
procedure Clear(fileName: string);
end;

Expand All @@ -86,6 +96,137 @@ implementation

{ TPublishDiagnostics }

procedure TPublishDiagnostics.ClearUserMessages;
begin
DiagnosticParams.uri := '';
fUserMessages.Clear;
end;

procedure TPublishDiagnostics.ClearCodeToolErrors(fileName: string; Range: TRange = nil);
var
CodeToolErrorsDiagnostics: TDiagnosticItems;
I: Integer;
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;

if Range = nil then
CodeToolErrorsDiagnostics.Clear
else
for I := 0 to CodeToolErrorsDiagnostics.Count - 1 do
begin
Diagnostic := CodeToolErrorsDiagnostics.Items[I];
if Range.InRange(Diagnostic.range.start.line, Diagnostic.range.start.character, True) then
CodeToolErrorsDiagnostics.Delete(I);
end;
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, eline, ecolumn, 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, eline, ecolumn);
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);
Expand All @@ -96,7 +237,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;
Expand All @@ -111,23 +256,77 @@ 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;

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);
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/LSP.Options.pas
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ constructor TTextDocumentSyncOptions.Create;
begin
Inherited Create;
openClose := True;
change := TTextDocumentSyncKind.Full;
change := TTextDocumentSyncKind.Incremental;
fSave:=TSaveOptions.Create(False);
end;

Expand Down
Loading
Loading