Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 11 additions & 11 deletions src/Compiler/Service/FSharpWorkspaceQuery.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,28 @@ module FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery
open System
open System.Collections.Generic
open FSharp.Compiler.Diagnostics
open System.Threading

open FSharp.Compiler.CodeAnalysis

open Internal.Utilities.DependencyGraph
open Internal.Utilities.Hashing
open Internal.Utilities.Library.Extras
open FSharpWorkspaceState
open Internal.Utilities.Library

#nowarn "57"

[<Experimental("This FCS API is experimental and subject to change.")>]
type FSharpDiagnosticReport internal (diagnostics, resultId: int) =
type FSharpDiagnosticReport internal (diagnostics, resultId: string) =

member _.Diagnostics = diagnostics

/// The result ID of the diagnostics. This needs to be unique for each version of the document in order to be able to clear old diagnostics.
member _.ResultId = resultId.ToString()
member _.ResultId = resultId

[<Experimental("This FCS API is experimental and subject to change.")>]
type FSharpWorkspaceQuery internal (depGraph: IThreadSafeDependencyGraph<_, _>, checker: FSharpChecker) =

let mutable resultIdCounter = 0

// TODO: we might need something more sophisticated eventually
// for now it's important that the result id is unique every time
// in order to be able to clear previous diagnostics
let getDiagnosticResultId () = Interlocked.Increment(&resultIdCounter)

member internal _.Checker = checker

member _.GetProjectSnapshot projectIdentifier =
Expand Down Expand Up @@ -86,6 +79,8 @@ type FSharpWorkspaceQuery internal (depGraph: IThreadSafeDependencyGraph<_, _>,
use _ =
Activity.start "GetDiagnosticsForFile" [ Activity.Tags.fileName, file.LocalPath ]

let projectSnapshot = this.GetProjectSnapshotForFile file

this.GetParseAndCheckResultsForFile file
|> Async.map (fun results ->
let diagnostics =
Expand All @@ -94,7 +89,12 @@ type FSharpWorkspaceQuery internal (depGraph: IThreadSafeDependencyGraph<_, _>,
| Some parseResult, _ -> parseResult.Diagnostics
| _ -> [||]

FSharpDiagnosticReport(diagnostics, getDiagnosticResultId ()))
let resultId =
projectSnapshot
|> Option.map (fun snapshot -> snapshot.ProjectSnapshot.FullVersion |> Md5Hasher.toString)
|> Option.defaultValue "empty"

FSharpDiagnosticReport(diagnostics, resultId))

member this.GetSemanticClassification(file: Uri) =
use _ =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type CapabilitiesManager(config: FSharpLanguageServerConfig, scOverrides: IServe
(DiagnosticOptions(
WorkDoneProgress = true,
InterFileDependencies = true,
Identifier = "potato",
WorkspaceDiagnostics = true
Identifier = "syntax",
WorkspaceDiagnostics = false
)),
//CompletionProvider = CompletionOptions(TriggerCharacters = [| "."; " " |], ResolveProvider = true, WorkDoneProgress = true),
//HoverProvider = SumType<bool, HoverOptions>(HoverOptions(WorkDoneProgress = true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ open FSharp.Compiler.LanguageServer.Common
open FSharp.Compiler.LanguageServer
open System.Threading.Tasks
open System.Threading
open System.Collections.Generic
open Microsoft.VisualStudio.FSharp.Editor

#nowarn "57"
Expand All @@ -18,7 +17,7 @@ type LanguageFeaturesHandler() =

interface IRequestHandler<
DocumentDiagnosticParams,
SumType<RelatedFullDocumentDiagnosticReport, RelatedUnchangedDocumentDiagnosticReport>,
SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>,
FSharpRequestContext
> with
[<LanguageServerEndpoint(Methods.TextDocumentDiagnosticName, LanguageServerConstants.DefaultLanguageName)>]
Expand All @@ -29,23 +28,19 @@ type LanguageFeaturesHandler() =

let! fsharpDiagnosticReport = context.Workspace.Query.GetDiagnosticsForFile request.TextDocument.Uri

let report =
FullDocumentDiagnosticReport(
Items = (fsharpDiagnosticReport.Diagnostics |> Array.map (_.ToLspDiagnostic())),
ResultId = fsharpDiagnosticReport.ResultId
)

let relatedDocuments = Dictionary()

relatedDocuments.Add(
request.TextDocument.Uri,
SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport> report
)

return
SumType<RelatedFullDocumentDiagnosticReport, RelatedUnchangedDocumentDiagnosticReport>(
RelatedFullDocumentDiagnosticReport(RelatedDocuments = relatedDocuments)
)
if request.PreviousResultId = fsharpDiagnosticReport.ResultId then
return
SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>(
UnchangedDocumentDiagnosticReport(ResultId = fsharpDiagnosticReport.ResultId)
)
else
return
SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>(
FullDocumentDiagnosticReport(
Items = (fsharpDiagnosticReport.Diagnostics |> Array.map (_.ToLspDiagnostic())),
ResultId = fsharpDiagnosticReport.ResultId
)
)
}
|> CancellableTask.start cancellationToken

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace FSharp.VisualStudio.Extension;
using System.Threading;
using System.Threading.Tasks;
using FSharp.Compiler.CodeAnalysis.Workspace;
using FSharp.Compiler.Diagnostics;
using FSharp.Compiler.LanguageServer;
using FSharp.Compiler.LanguageServer.Common;

Expand Down Expand Up @@ -47,31 +46,8 @@ public ServerCapabilities OverrideServerCapabilities(FSharpLanguageServerConfig
var capabilities = new VSInternalServerCapabilities
{
TextDocumentSync = value.TextDocumentSync,
SupportsDiagnosticRequests = true,
ProjectContextProvider = true,
DiagnosticProvider =
config.EnabledFeatures.Diagnostics ?

new()
{
SupportsMultipleContextsDiagnostics = true,
DiagnosticKinds = [
// Support a specialized requests dedicated to task-list items. This way the client can ask just
// for these, independently of other diagnostics. They can also throttle themselves to not ask if
// the task list would not be visible.
//VSInternalDiagnosticKind.Task,
// Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on.
VSInternalDiagnosticKind.Syntax,
// Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic
// requests, allowing the former to quickly reach the user without blocking on the latter. In a
// similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing
// the former to appear as soon as possible as they are much more critical for the user and should
// not be delayed by a slow analyzer.
//new("Semantic"),
//new(PullDiagnosticCategories.DocumentAnalyzerSyntax),
//new(PullDiagnosticCategories.DocumentAnalyzerSemantic),
]
} : null,
DiagnosticOptions = value.DiagnosticOptions,
SemanticTokensOptions = config.EnabledFeatures.SemanticHighlighting ? new()
{
Legend = new()
Expand All @@ -95,28 +71,11 @@ public ServerCapabilities OverrideServerCapabilities(FSharpLanguageServerConfig
}
}

internal class VsDiagnosticsHandler
: IRequestHandler<VSInternalDiagnosticParams, VSInternalDiagnosticReport[], FSharpRequestContext>,
IRequestHandler<VSGetProjectContextsParams, VSProjectContextList, FSharpRequestContext>
internal class VsProjectContextHandler
: IRequestHandler<VSGetProjectContextsParams, VSProjectContextList, FSharpRequestContext>
{
public bool MutatesSolutionState => false;

[LanguageServerEndpoint(VSInternalMethods.DocumentPullDiagnosticName, LanguageServerConstants.DefaultLanguageName)]
public async Task<VSInternalDiagnosticReport[]> HandleRequestAsync(VSInternalDiagnosticParams request, FSharpRequestContext context, CancellationToken cancellationToken)
{
var report = await context.Workspace.Query.GetDiagnosticsForFile(request!.TextDocument!.Uri).Please(cancellationToken);

var vsReport = new VSInternalDiagnosticReport
{
ResultId = report.ResultId,
//Identifier = 1,
//Version = 1,
Diagnostics = [.. report.Diagnostics.Select(FSharpDiagnosticExtensions.ToLspDiagnostic)]
};

return [vsReport];
}

[LanguageServerEndpoint("textDocument/_vs_getProjectContexts", LanguageServerConstants.DefaultLanguageName)]
public Task<VSProjectContextList> HandleRequestAsync(VSGetProjectContextsParams request, FSharpRequestContext context, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -375,7 +334,7 @@ await this.Extensibility.Settings().WriteAsync(batch =>
var ((inputStream, outputStream), _server) = FSharpLanguageServer.Create(workspace, serverConfig, (serviceCollection) =>
{
serviceCollection.AddSingleton<IServerCapabilitiesOverride, VsServerCapabilitiesOverride>();
serviceCollection.AddSingleton<IMethodHandler, VsDiagnosticsHandler>();
serviceCollection.AddSingleton<IMethodHandler, VsProjectContextHandler>();
});

var solutions = await ws.QuerySolutionAsync(
Expand Down
23 changes: 6 additions & 17 deletions tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,12 @@ let ``Basic server workflow`` () =
)

let! diagnosticsResponse =
client.JsonRpc.InvokeAsync<SumType<RelatedFullDocumentDiagnosticReport, RelatedUnchangedDocumentDiagnosticReport>>(
client.JsonRpc.InvokeAsync<SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>>(
Methods.TextDocumentDiagnosticName,
DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
)

Assert.Equal(
0,
(diagnosticsResponse.First.RelatedDocuments
|> Seq.head
|> _.Value.First.Items.Length)
)
Assert.Equal(0, diagnosticsResponse.First.Items.Length)

let contentEdit = $"{contentOnDisk}\nx <- 2"

Expand All @@ -122,13 +117,12 @@ let ``Basic server workflow`` () =
)

let! diagnosticsResponse =
client.JsonRpc.InvokeAsync<SumType<RelatedFullDocumentDiagnosticReport, RelatedUnchangedDocumentDiagnosticReport>>(
client.JsonRpc.InvokeAsync<SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>>(
Methods.TextDocumentDiagnosticName,
DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
)

let diagnostics =
diagnosticsResponse.First.RelatedDocuments |> Seq.head |> _.Value.First.Items
let diagnostics = diagnosticsResponse.First.Items

Assert.Equal(1, diagnostics.Length)
Assert.Contains("This value is not mutable", diagnostics[0].Message)
Expand All @@ -140,18 +134,13 @@ let ``Basic server workflow`` () =
)

let! diagnosticsResponse =
client.JsonRpc.InvokeAsync<SumType<RelatedFullDocumentDiagnosticReport, RelatedUnchangedDocumentDiagnosticReport>>(
client.JsonRpc.InvokeAsync<SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>>(
Methods.TextDocumentDiagnosticName,
DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
)

// We didn't save the file, so it should be again read from disk and have no diagnostics
Assert.Equal(
0,
(diagnosticsResponse.First.RelatedDocuments
|> Seq.head
|> _.Value.First.Items.Length)
)
Assert.Equal(0, diagnosticsResponse.First.Items.Length)
}

[<Fact>]
Expand Down
Loading