Skip to content

Commit 04d4c51

Browse files
brettfobaronfel
authored andcommitted
add workspace and diagnostics to lsp (#7006)
1 parent 837c6cb commit 04d4c51

File tree

9 files changed

+397
-21
lines changed

9 files changed

+397
-21
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<Project DefaultTargets="Build">
2+
3+
<!--
4+
5+
Usage: dotnet build <this-file> /p:ProjectFile=<path-to-project>
6+
Returns: The project's supported target frameworks, one per line, in the format of:
7+
DetectedTargetFramework=<tfm-1>
8+
DetectedTargetFramework=<tfm-2>
9+
...
10+
11+
Usage: dotnet build <this-file> /p:ProjectFile=<path-to-project> /p:TargetFramework=<tfm>
12+
Returns: The project's command line arguments, one per line, as they would be passed to fsc.exe in the format of:
13+
DetectedCommandLineArg=<arg-1>
14+
DetectedCommandLineArg=<arg-2>
15+
...
16+
17+
To avoid recreating a distribution of MSBuild just to get some evaluated project values, we instead inject a special
18+
targets file into the specified project which then prints the values with a well-known prefix that's easy to parse.
19+
20+
The benefits of doing it this way are:
21+
- We don't have to re-create a copy of MSBuild. That would be difficult, large, and change frequently.
22+
- This works on any OS and any TFM.
23+
- We use the exact version of MSBuild and the compiler that the user has instead of dealing with the potential
24+
mismatch between what this tool would include vs. what they actually have.
25+
26+
The downsides of this method are:
27+
- We're dependent upon the continued existence of some well-known Targets and ItemGroups, but the ones we depend on
28+
have been stable for some time.
29+
- An external process invoke is slow, but this is only done once at LSP instantiation.
30+
31+
-->
32+
33+
<PropertyGroup>
34+
<!-- See this file for details on the magic. -->
35+
<InjetedTargetsFile>$(MSBuildThisFileDirectory)FSharp.Compiler.LanguageServer.DesignTime.targets</InjetedTargetsFile>
36+
</PropertyGroup>
37+
38+
<Target Name="Build">
39+
<Error Text="Property `ProjectFile` must be specified." Condition="'$(ProjectFile)' == ''" />
40+
41+
<!-- report TFMs if none specified -->
42+
<MSBuild Projects="$(ProjectFile)" Targets="ReportTargetFrameworks" Properties="CustomAfterMicrosoftCommonCrossTargetingTargets=$(InjetedTargetsFile);CustomAfterMicrosoftCommonTargets=$(InjetedTargetsFile)" Condition="'$(TargetFramework)' == ''" />
43+
44+
<!-- report command line arguments when TFM is given -->
45+
<MSBuild Projects="$(ProjectFile)" Targets="Restore" Condition="'$(TargetFramework)' != ''" />
46+
<MSBuild Projects="$(ProjectFile)" Targets="ReportCommandLineArgs" Properties="DesignTimeBuild=true;CustomAfterMicrosoftCommonTargets=$(InjetedTargetsFile);TargetFramework=$(TargetFramework)" Condition="'$(TargetFramework)' != ''" />
47+
</Target>
48+
49+
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Project>
2+
3+
<!--
4+
Accurately reporting the command line arguments requires that the target Restore is run _without_ these values having
5+
been set. The project file helper associated with this .targets file calls Restore before calling into the target
6+
that prints the command line arguments.
7+
-->
8+
<PropertyGroup Condition="'$(DesignTimeBuild)' == 'true'">
9+
<ProvideCommandLineArgs>true</ProvideCommandLineArgs>
10+
<BuildProjectReferences>false</BuildProjectReferences>
11+
<SkipCompilerExecution>true</SkipCompilerExecution>
12+
<DisableRarCache>true</DisableRarCache>
13+
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
14+
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
15+
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
16+
<SkipCopyBuildProduct>true</SkipCopyBuildProduct>
17+
<AddModules>false</AddModules>
18+
<UseCommonOutputDirectory>true</UseCommonOutputDirectory>
19+
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
20+
</PropertyGroup>
21+
22+
<!-- displays the target frameworks of a cross-targeted project -->
23+
<Target Name="_ComputeDependsOn">
24+
<!--
25+
In a multi-targeted project, the _ComputeTargetFrameworkItems populates the ItemGroup _TargetFramework with the
26+
appropriate values. If the project contains just a single value for $(TargetFramework) then the target
27+
_ComputeTargetFrameworkItems doesn't exist, so instead the helper target _PopulateTargetFrameworks is run which
28+
populates the same group.
29+
-->
30+
<PropertyGroup>
31+
<WorkerDependsOn Condition="'$(TargetFramework)' == ''">_ComputeTargetFrameworkItems</WorkerDependsOn>
32+
<WorkerDependsOn Condition="'$(TargetFramework)' != ''">_PopulateTargetFrameworks</WorkerDependsOn>
33+
</PropertyGroup>
34+
</Target>
35+
<Target Name="_PopulateTargetFrameworks">
36+
<ItemGroup>
37+
<_TargetFramework Include="$(TargetFramework)" />
38+
</ItemGroup>
39+
</Target>
40+
<Target Name="_ReportTargetFrameworksWorker" DependsOnTargets="_ComputeDependsOn;$(WorkerDependsOn)">
41+
<Message Text="DetectedTargetFramework=%(_TargetFramework.Identity)" Importance="High" />
42+
</Target>
43+
<Target Name="ReportTargetFrameworks" DependsOnTargets="_ComputeDependsOn;_ReportTargetFrameworksWorker">
44+
</Target>
45+
46+
<!-- displays the command line arguments passed to fsc.exe -->
47+
<Target Name="ReportCommandLineArgs"
48+
DependsOnTargets="Build">
49+
<Message Text="DetectedCommandLineArg=%(FscCommandLineArgs.Identity)" Importance="High" />
50+
</Target>
51+
52+
</Project>

src/fsharp/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323
<Compile Include="Program.fs" />
2424
</ItemGroup>
2525

26+
<ItemGroup>
27+
<None Include="FSharp.Compiler.LanguageServer.DesignTime.proj" CopyToOutputDirectory="PreserveNewest" />
28+
<None Include="FSharp.Compiler.LanguageServer.DesignTime.targets" CopyToOutputDirectory="PreserveNewest" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<InternalsVisibleTo Include="FSharp.Compiler.LanguageServer.UnitTests" />
33+
</ItemGroup>
34+
2635
<ItemGroup>
2736
<ProjectReference Include="$(MSBuildThisFileDirectory)..\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj" />
2837
<ProjectReference Include="$(MSBuildThisFileDirectory)..\FSharp.Core\FSharp.Core.fsproj" />

src/fsharp/FSharp.Compiler.LanguageServer/LspExternalAccess.fs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@ module FunctionNames =
99
[<Literal>]
1010
let OptionsSet = "options/set"
1111

12+
[<Literal>]
13+
let TextDocumentPublishDiagnostics = "textDocument/publishDiagnostics"
14+
1215
type Options =
13-
{ usePreviewTextHover: bool }
16+
{ usePreviewTextHover: bool
17+
usePreviewDiagnostics: bool }
1418
static member Default() =
15-
{ usePreviewTextHover = false }
19+
{ usePreviewTextHover = false
20+
usePreviewDiagnostics = false }
21+
static member AllOn() =
22+
{ usePreviewTextHover = true
23+
usePreviewDiagnostics = true }
1624

1725
module Extensions =
1826
type JsonRpc with

src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ namespace FSharp.Compiler.LanguageServer
55
open Newtonsoft.Json.Linq
66
open Newtonsoft.Json
77

8-
// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specification. The properties on
9-
// these types are camlCased to match the underlying JSON properties to avoid attributes on every field:
8+
// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/.
9+
// The properties on these types are camlCased to match the underlying JSON properties to avoid attributes on every
10+
// field:
1011
// [<JsonProperty("camlCased")>]
1112

1213
/// Represents a zero-based line and column of a text document.
@@ -32,7 +33,7 @@ type Diagnostic =
3233
{ range: Range
3334
severity: int option
3435
code: string
35-
source: string option // "F#"
36+
source: string option
3637
message: string
3738
relatedInformation: DiagnosticRelatedInformation[] option }
3839
static member Error = 1
@@ -46,7 +47,7 @@ type PublishDiagnosticsParams =
4647

4748
type ClientCapabilities =
4849
{ workspace: JToken option // TODO: WorkspaceClientCapabilities
49-
textDocument: JToken option // TODO: TextDocumentCapabilities
50+
textDocument: JToken option // TODO: TextDocumentClientCapabilities, publishDiagnostics: { relatedInformation: bool option }
5051
experimental: JToken option
5152
supportsVisualStudioExtensions: bool option }
5253

src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ open System.Threading
88
open Newtonsoft.Json.Linq
99
open StreamJsonRpc
1010

11-
// https://microsoft.github.io/language-server-protocol/specification
12-
type Methods(state: State) =
11+
// https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
12+
type Methods() =
13+
14+
let state = State()
1315

1416
/// Helper to run Async<'T> with a CancellationToken.
1517
let runAsync (cancellationToken: CancellationToken) (computation: Async<'T>) = Async.StartAsTask(computation, cancellationToken=cancellationToken)
@@ -29,8 +31,10 @@ type Methods(state: State) =
2931
[<Optional; DefaultParameterValue(null: JToken)>] initializationOptions: JToken,
3032
capabilities: ClientCapabilities,
3133
[<Optional; DefaultParameterValue(null: string)>] trace: string,
32-
[<Optional; DefaultParameterValue(null: WorkspaceFolder[])>] workspaceFolders: WorkspaceFolder[]
34+
[<Optional; DefaultParameterValue(null: WorkspaceFolder[])>] workspaceFolders: WorkspaceFolder[],
35+
[<Optional; DefaultParameterValue(CancellationToken())>] cancellationToken: CancellationToken
3336
) =
37+
state.Initialize rootPath rootUri (fun projectOptions -> TextDocument.PublishDiagnostics(state, projectOptions) |> Async.Start)
3438
{ InitializeResult.capabilities = ServerCapabilities.DefaultCapabilities() }
3539

3640
[<JsonRpcMethod("initialized")>]
@@ -63,5 +67,6 @@ type Methods(state: State) =
6367
(
6468
options: Options
6569
) =
66-
sprintf "got options %A" options |> Console.Error.WriteLine
70+
eprintfn "got options %A" options
6771
state.Options <- options
72+
state.InvalidateAllProjects()

src/fsharp/FSharp.Compiler.LanguageServer/Server.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ type Server(sendingStream: Stream, receivingStream: Stream) =
1212
let converter = JsonOptionConverter() // special handler to convert between `Option<'T>` and `obj/null`.
1313
do formatter.JsonSerializer.Converters.Add(converter)
1414
let handler = new HeaderDelimitedMessageHandler(sendingStream, receivingStream, formatter)
15-
let state = State()
16-
let methods = Methods(state)
15+
let methods = Methods()
1716
let rpc = new JsonRpc(handler, methods)
17+
do methods.State.JsonRpc <- Some rpc
1818

1919
member __.StartListening() =
2020
rpc.StartListening()
2121

2222
member __.WaitForExitAsync() =
2323
async {
24-
do! Async.AwaitEvent (state.Shutdown)
25-
do! Async.AwaitEvent (state.Exit)
24+
do! Async.AwaitEvent (methods.State.Shutdown)
25+
do! Async.AwaitEvent (methods.State.Exit)
2626
}
2727

2828
interface IDisposable with

0 commit comments

Comments
 (0)