Skip to content

Commit a4cbfe1

Browse files
brettfocartermp
andauthored
enable preview LSP support in VS (#6945)
* enable preview LSP support Includes plumbing and a stub for `textDocument/hover` (e.g., QuickInfo). * enable easy exclusion of the language server from VS components * Update src/fsharp/FSharp.Compiler.LanguageServer/TextDocument.fs Co-Authored-By: Phillip Carter <pcarter@fastmail.com>
1 parent e556508 commit a4cbfe1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+698
-117
lines changed

VisualFSharp.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageSer
158158
EndProject
159159
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageServer.UnitTests", "tests\FSharp.Compiler.LanguageServer.UnitTests\FSharp.Compiler.LanguageServer.UnitTests.fsproj", "{AAF2D233-1C38-4090-8FFA-F7C545625E06}"
160160
EndProject
161+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Editor.Helpers", "vsintegration\src\FSharp.Editor.Helpers\FSharp.Editor.Helpers.csproj", "{79255A92-ED00-40BA-9D64-12FCC664A976}"
162+
EndProject
161163
Global
162164
GlobalSection(SolutionConfigurationPlatforms) = preSolution
163165
Debug|Any CPU = Debug|Any CPU
@@ -912,6 +914,18 @@ Global
912914
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|Any CPU.Build.0 = Release|Any CPU
913915
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.ActiveCfg = Release|Any CPU
914916
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.Build.0 = Release|Any CPU
917+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
918+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.Build.0 = Debug|Any CPU
919+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.ActiveCfg = Debug|Any CPU
920+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.Build.0 = Debug|Any CPU
921+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.ActiveCfg = Release|Any CPU
922+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.Build.0 = Release|Any CPU
923+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.ActiveCfg = Release|Any CPU
924+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.Build.0 = Release|Any CPU
925+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.ActiveCfg = Release|Any CPU
926+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.Build.0 = Release|Any CPU
927+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.ActiveCfg = Release|Any CPU
928+
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.Build.0 = Release|Any CPU
915929
EndGlobalSection
916930
GlobalSection(SolutionProperties) = preSolution
917931
HideSolutionNode = FALSE
@@ -986,6 +1000,7 @@ Global
9861000
{8EC30B2E-F1F9-4A98-BBB5-DD0CF6C84DDC} = {647810D0-5307-448F-99A2-E83917010DAE}
9871001
{60BAFFA5-6631-4328-B044-2E012AB76DCA} = {B8DDA694-7939-42E3-95E5-265C2217C142}
9881002
{AAF2D233-1C38-4090-8FFA-F7C545625E06} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449}
1003+
{79255A92-ED00-40BA-9D64-12FCC664A976} = {4C7B48D7-19AF-4AE7-9D1D-3BB289D5480D}
9891004
EndGlobalSection
9901005
GlobalSection(ExtensibilityGlobals) = postSolution
9911006
SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37}

eng/Signing.props

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project>
2+
3+
<ItemGroup>
4+
<FileSignInfo Include="Nerdbank.Streams.dll" CertificateName="None" />
5+
<FileSignInfo Include="Newtonsoft.Json.dll" CertificateName="None" />
6+
</ItemGroup>
7+
8+
</Project>

eng/Versions.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<SystemThreadingTasksParallelVersion>4.3.0</SystemThreadingTasksParallelVersion>
9393
<SystemThreadingThreadVersion>4.3.0</SystemThreadingThreadVersion>
9494
<SystemThreadingThreadPoolVersion>4.3.0</SystemThreadingThreadPoolVersion>
95-
<SystemValueTupleVersion>4.4.0</SystemValueTupleVersion>
95+
<SystemValueTupleVersion>4.5.0</SystemValueTupleVersion>
9696
<!-- Roslyn packages -->
9797
<MicrosoftCodeAnalysisEditorFeaturesVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesVersion>
9898
<MicrosoftCodeAnalysisEditorFeaturesTextVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesTextVersion>
@@ -115,6 +115,7 @@
115115
<MicrosoftVisualStudioEditorVersion>16.0.467</MicrosoftVisualStudioEditorVersion>
116116
<MicrosoftVisualStudioImageCatalogVersion>16.0.28727</MicrosoftVisualStudioImageCatalogVersion>
117117
<MicrosoftVisualStudioImagingVersion>16.0.28729</MicrosoftVisualStudioImagingVersion>
118+
<MicrosoftVisualStudioLanguageServerClientVersion>16.1.3121</MicrosoftVisualStudioLanguageServerClientVersion>
118119
<MicrosoftVisualStudioLanguageStandardClassificationVersion>16.0.467</MicrosoftVisualStudioLanguageStandardClassificationVersion>
119120
<MicrosoftVisualStudioLanguageVersion>16.0.467</MicrosoftVisualStudioLanguageVersion>
120121
<MicrosoftVisualStudioLanguageIntellisenseVersion>16.0.467</MicrosoftVisualStudioLanguageIntellisenseVersion>

eng/targets/Settings.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@
77
<NuspecBasePath>$(ArtifactsBinDir)</NuspecBasePath>
88
</PropertyGroup>
99

10+
<PropertyGroup>
11+
<IncludeVsLanguageServer>true</IncludeVsLanguageServer>
12+
</PropertyGroup>
13+
1014
</Project>

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<Compile Include="State.fs" />
14+
<Compile Include="JsonDUConverter.fs" />
15+
<Compile Include="JsonOptionConverter.fs" />
1516
<Compile Include="LspTypes.fs" />
17+
<Compile Include="LspExternalAccess.fs" />
18+
<Compile Include="State.fs" />
1619
<Compile Include="TextDocument.fs" />
1720
<Compile Include="Methods.fs" />
1821
<Compile Include="Server.fs" />
@@ -28,4 +31,19 @@
2831
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcVersion)" />
2932
</ItemGroup>
3033

34+
<Target Name="GatherPublishedProjectOutputGroupItems"
35+
DependsOnTargets="Publish">
36+
<ItemGroup>
37+
<_PublishedProjectOutputGroupFiles Include="$(PublishDir)\**" />
38+
</ItemGroup>
39+
</Target>
40+
41+
<Target Name="PublishedProjectOutputGroup"
42+
Returns="@(PublishedProjectOutputGroupOutput)"
43+
DependsOnTargets="GatherPublishedProjectOutputGroupItems">
44+
<ItemGroup>
45+
<PublishedProjectOutputGroupOutput Include="@(_PublishedProjectOutputGroupFiles)" TargetPath="Agent\%(RecursiveDir)%(FileName)%(Extension)" />
46+
</ItemGroup>
47+
</Target>
48+
3149
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.LanguageServer
4+
5+
open System
6+
open FSharp.Reflection
7+
open Newtonsoft.Json
8+
9+
type JsonDUConverter() =
10+
inherit JsonConverter()
11+
override __.CanConvert(typ) = FSharpType.IsUnion(typ)
12+
override __.WriteJson(writer, value, _serializer) =
13+
writer.WriteValue(value.ToString().ToLowerInvariant())
14+
override __.ReadJson(reader, typ, x, serializer) =
15+
let cases = FSharpType.GetUnionCases(typ)
16+
let str = serializer.Deserialize(reader, typeof<string>) :?> string
17+
let case = cases |> Array.find (fun c -> String.Compare(c.Name, str, StringComparison.OrdinalIgnoreCase) = 0)
18+
FSharpValue.MakeUnion(case, [||])
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.LanguageServer
4+
5+
open System
6+
open FSharp.Reflection
7+
open Newtonsoft.Json
8+
9+
type JsonOptionConverter() =
10+
inherit JsonConverter()
11+
override __.CanConvert(typ) = typ.IsGenericType && typ.GetGenericTypeDefinition() = typedefof<option<_>>
12+
override __.WriteJson(writer, value, serializer) =
13+
let value = match value with
14+
| null -> null
15+
| _ ->
16+
let _, fields = FSharpValue.GetUnionFields(value, value.GetType())
17+
fields.[0]
18+
serializer.Serialize(writer, value)
19+
override __.ReadJson(reader, typ, _, serializer) =
20+
let innerType = typ.GetGenericArguments().[0]
21+
let innerType =
22+
if innerType.IsValueType then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
23+
else innerType
24+
let value = serializer.Deserialize(reader, innerType)
25+
let cases = FSharpType.GetUnionCases(typ)
26+
if value = null then FSharpValue.MakeUnion(cases.[0], [||])
27+
else FSharpValue.MakeUnion(cases.[1], [|value|])
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.LanguageServer
4+
5+
open StreamJsonRpc
6+
7+
[<AutoOpen>]
8+
module FunctionNames =
9+
[<Literal>]
10+
let OptionsSet = "options/set"
11+
12+
type Options =
13+
{ usePreviewTextHover: bool }
14+
static member Default() =
15+
{ usePreviewTextHover = false }
16+
17+
module Extensions =
18+
type JsonRpc with
19+
member jsonRpc.SetOptionsAsync (options: Options) =
20+
async {
21+
do! jsonRpc.InvokeAsync(OptionsSet, options) |> Async.AwaitTask
22+
}

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
namespace FSharp.Compiler.LanguageServer
44

5-
// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specification. The properties on these
6-
// types are camlCased to match the underlying JSON properties to avoid attributes on every field:
5+
open Newtonsoft.Json.Linq
6+
open Newtonsoft.Json
7+
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:
710
// [<JsonProperty("camlCased")>]
811

912
/// Represents a zero-based line and column of a text document.
@@ -27,11 +30,11 @@ type DiagnosticRelatedInformation =
2730

2831
type Diagnostic =
2932
{ range: Range
30-
severity: int
33+
severity: int option
3134
code: string
32-
source: string // "F#"
35+
source: string option // "F#"
3336
message: string
34-
relatedInformation: DiagnosticRelatedInformation[] }
37+
relatedInformation: DiagnosticRelatedInformation[] option }
3538
static member Error = 1
3639
static member Warning = 2
3740
static member Information = 3
@@ -41,9 +44,23 @@ type PublishDiagnosticsParams =
4144
{ uri: DocumentUri
4245
diagnostics: Diagnostic[] }
4346

44-
type InitializeParams = string // TODO:
47+
type ClientCapabilities =
48+
{ workspace: JToken option // TODO: WorkspaceClientCapabilities
49+
textDocument: JToken option // TODO: TextDocumentCapabilities
50+
experimental: JToken option
51+
supportsVisualStudioExtensions: bool option }
52+
53+
[<JsonConverter(typeof<JsonDUConverter>)>]
54+
type Trace =
55+
| Off
56+
| Messages
57+
| Verbose
4558

46-
// Note, this type has many more optional values that can be expanded as support is added.
59+
type WorkspaceFolder =
60+
{ uri: DocumentUri
61+
name: string }
62+
63+
/// Note, this type has many more optional values that can be expanded as support is added.
4764
type ServerCapabilities =
4865
{ hoverProvider: bool }
4966
static member DefaultCapabilities() =
@@ -52,6 +69,7 @@ type ServerCapabilities =
5269
type InitializeResult =
5370
{ capabilities: ServerCapabilities }
5471

72+
[<JsonConverter(typeof<JsonDUConverter>)>]
5573
type MarkupKind =
5674
| PlainText
5775
| Markdown
@@ -66,7 +84,3 @@ type Hover =
6684

6785
type TextDocumentIdentifier =
6886
{ uri: DocumentUri }
69-
70-
type TextDocumentPositionParams =
71-
{ textDocument: TextDocumentIdentifier
72-
position: Position }

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

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,66 @@
22

33
namespace FSharp.Compiler.LanguageServer
44

5+
open System
6+
open System.Runtime.InteropServices
7+
open System.Threading
8+
open Newtonsoft.Json.Linq
59
open StreamJsonRpc
610

711
// https://microsoft.github.io/language-server-protocol/specification
812
type Methods(state: State) =
913

14+
/// Helper to run Async<'T> with a CancellationToken.
15+
let runAsync (cancellationToken: CancellationToken) (computation: Async<'T>) = Async.StartAsTask(computation, cancellationToken=cancellationToken)
16+
17+
member __.State = state
18+
19+
//--------------------------------------------------------------------------
20+
// official LSP methods
21+
//--------------------------------------------------------------------------
22+
1023
[<JsonRpcMethod("initialize")>]
11-
member __.Initialize (args: InitializeParams) =
12-
async {
13-
// note, it's important that this method is `async` because unit tests can then properly verify that the
14-
// JSON RPC handling of async methods is correct
15-
return ServerCapabilities.DefaultCapabilities()
16-
} |> Async.StartAsTask
24+
member __.Initialize
25+
(
26+
processId: Nullable<int>,
27+
[<Optional; DefaultParameterValue(null: string)>] rootPath: string,
28+
[<Optional; DefaultParameterValue(null: string)>] rootUri: DocumentUri,
29+
[<Optional; DefaultParameterValue(null: JToken)>] initializationOptions: JToken,
30+
capabilities: ClientCapabilities,
31+
[<Optional; DefaultParameterValue(null: string)>] trace: string,
32+
[<Optional; DefaultParameterValue(null: WorkspaceFolder[])>] workspaceFolders: WorkspaceFolder[]
33+
) =
34+
{ InitializeResult.capabilities = ServerCapabilities.DefaultCapabilities() }
35+
36+
[<JsonRpcMethod("initialized")>]
37+
member __.Initialized () = ()
1738

1839
[<JsonRpcMethod("shutdown")>]
19-
member __.Shutdown() = state.DoShutdown()
40+
member __.Shutdown(): obj = state.DoShutdown(); null
41+
42+
[<JsonRpcMethod("exit")>]
43+
member __.Exit() = state.DoExit()
44+
45+
[<JsonRpcMethod("$/cancelRequest")>]
46+
member __.cancelRequest (id: JToken) = state.DoCancel()
2047

2148
[<JsonRpcMethod("textDocument/hover")>]
22-
member __.TextDocumentHover (args: TextDocumentPositionParams) = TextDocument.Hover(state, args) |> Async.StartAsTask
49+
member __.TextDocumentHover
50+
(
51+
textDocument: TextDocumentIdentifier,
52+
position: Position,
53+
[<Optional; DefaultParameterValue(CancellationToken())>] cancellationToken: CancellationToken
54+
) =
55+
TextDocument.Hover state textDocument position |> runAsync cancellationToken
56+
57+
//--------------------------------------------------------------------------
58+
// unofficial LSP methods that we implement separately
59+
//--------------------------------------------------------------------------
60+
61+
[<JsonRpcMethod(OptionsSet)>]
62+
member __.OptionsSet
63+
(
64+
options: Options
65+
) =
66+
sprintf "got options %A" options |> Console.Error.WriteLine
67+
state.Options <- options

0 commit comments

Comments
 (0)