Skip to content
Merged
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
15 changes: 15 additions & 0 deletions VisualFSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageSer
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Editor.Helpers", "vsintegration\src\FSharp.Editor.Helpers\FSharp.Editor.Helpers.csproj", "{79255A92-ED00-40BA-9D64-12FCC664A976}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -912,6 +914,18 @@ Global
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|Any CPU.Build.0 = Release|Any CPU
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.ActiveCfg = Release|Any CPU
{AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.Build.0 = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.ActiveCfg = Debug|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.Build.0 = Debug|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.ActiveCfg = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.Build.0 = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.ActiveCfg = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.Build.0 = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.Build.0 = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.ActiveCfg = Release|Any CPU
{79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -986,6 +1000,7 @@ Global
{8EC30B2E-F1F9-4A98-BBB5-DD0CF6C84DDC} = {647810D0-5307-448F-99A2-E83917010DAE}
{60BAFFA5-6631-4328-B044-2E012AB76DCA} = {B8DDA694-7939-42E3-95E5-265C2217C142}
{AAF2D233-1C38-4090-8FFA-F7C545625E06} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449}
{79255A92-ED00-40BA-9D64-12FCC664A976} = {4C7B48D7-19AF-4AE7-9D1D-3BB289D5480D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37}
Expand Down
8 changes: 8 additions & 0 deletions eng/Signing.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<ItemGroup>
<FileSignInfo Include="Nerdbank.Streams.dll" CertificateName="None" />
<FileSignInfo Include="Newtonsoft.Json.dll" CertificateName="None" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<SystemThreadingTasksParallelVersion>4.3.0</SystemThreadingTasksParallelVersion>
<SystemThreadingThreadVersion>4.3.0</SystemThreadingThreadVersion>
<SystemThreadingThreadPoolVersion>4.3.0</SystemThreadingThreadPoolVersion>
<SystemValueTupleVersion>4.4.0</SystemValueTupleVersion>
<SystemValueTupleVersion>4.5.0</SystemValueTupleVersion>
<!-- Roslyn packages -->
<MicrosoftCodeAnalysisEditorFeaturesVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesVersion>
<MicrosoftCodeAnalysisEditorFeaturesTextVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesTextVersion>
Expand All @@ -115,6 +115,7 @@
<MicrosoftVisualStudioEditorVersion>16.0.467</MicrosoftVisualStudioEditorVersion>
<MicrosoftVisualStudioImageCatalogVersion>16.0.28727</MicrosoftVisualStudioImageCatalogVersion>
<MicrosoftVisualStudioImagingVersion>16.0.28729</MicrosoftVisualStudioImagingVersion>
<MicrosoftVisualStudioLanguageServerClientVersion>16.1.3121</MicrosoftVisualStudioLanguageServerClientVersion>
<MicrosoftVisualStudioLanguageStandardClassificationVersion>16.0.467</MicrosoftVisualStudioLanguageStandardClassificationVersion>
<MicrosoftVisualStudioLanguageVersion>16.0.467</MicrosoftVisualStudioLanguageVersion>
<MicrosoftVisualStudioLanguageIntellisenseVersion>16.0.467</MicrosoftVisualStudioLanguageIntellisenseVersion>
Expand Down
4 changes: 4 additions & 0 deletions eng/targets/Settings.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
<NuspecBasePath>$(ArtifactsBinDir)</NuspecBasePath>
</PropertyGroup>

<PropertyGroup>
<IncludeVsLanguageServer>true</IncludeVsLanguageServer>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="State.fs" />
<Compile Include="JsonDUConverter.fs" />
<Compile Include="JsonOptionConverter.fs" />
<Compile Include="LspTypes.fs" />
<Compile Include="LspExternalAccess.fs" />
<Compile Include="State.fs" />
<Compile Include="TextDocument.fs" />
<Compile Include="Methods.fs" />
<Compile Include="Server.fs" />
Expand All @@ -28,4 +31,19 @@
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcVersion)" />
</ItemGroup>

<Target Name="GatherPublishedProjectOutputGroupItems"
DependsOnTargets="Publish">
<ItemGroup>
<_PublishedProjectOutputGroupFiles Include="$(PublishDir)\**" />
</ItemGroup>
</Target>

<Target Name="PublishedProjectOutputGroup"
Returns="@(PublishedProjectOutputGroupOutput)"
DependsOnTargets="GatherPublishedProjectOutputGroupItems">
<ItemGroup>
<PublishedProjectOutputGroupOutput Include="@(_PublishedProjectOutputGroupFiles)" TargetPath="Agent\%(RecursiveDir)%(FileName)%(Extension)" />
</ItemGroup>
</Target>

</Project>
18 changes: 18 additions & 0 deletions src/fsharp/FSharp.Compiler.LanguageServer/JsonDUConverter.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.LanguageServer

open System
open FSharp.Reflection
open Newtonsoft.Json

type JsonDUConverter() =
inherit JsonConverter()
override __.CanConvert(typ) = FSharpType.IsUnion(typ)
override __.WriteJson(writer, value, _serializer) =
writer.WriteValue(value.ToString().ToLowerInvariant())
override __.ReadJson(reader, typ, x, serializer) =
let cases = FSharpType.GetUnionCases(typ)
let str = serializer.Deserialize(reader, typeof<string>) :?> string
let case = cases |> Array.find (fun c -> String.Compare(c.Name, str, StringComparison.OrdinalIgnoreCase) = 0)
FSharpValue.MakeUnion(case, [||])
27 changes: 27 additions & 0 deletions src/fsharp/FSharp.Compiler.LanguageServer/JsonOptionConverter.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.LanguageServer

open System
open FSharp.Reflection
open Newtonsoft.Json

type JsonOptionConverter() =
inherit JsonConverter()
override __.CanConvert(typ) = typ.IsGenericType && typ.GetGenericTypeDefinition() = typedefof<option<_>>
override __.WriteJson(writer, value, serializer) =
let value = match value with
| null -> null
| _ ->
let _, fields = FSharpValue.GetUnionFields(value, value.GetType())
fields.[0]
serializer.Serialize(writer, value)
override __.ReadJson(reader, typ, _, serializer) =
let innerType = typ.GetGenericArguments().[0]
let innerType =
if innerType.IsValueType then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
else innerType
let value = serializer.Deserialize(reader, innerType)
let cases = FSharpType.GetUnionCases(typ)
if value = null then FSharpValue.MakeUnion(cases.[0], [||])
else FSharpValue.MakeUnion(cases.[1], [|value|])
22 changes: 22 additions & 0 deletions src/fsharp/FSharp.Compiler.LanguageServer/LspExternalAccess.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.LanguageServer

open StreamJsonRpc

[<AutoOpen>]
module FunctionNames =
[<Literal>]
let OptionsSet = "options/set"

type Options =
{ usePreviewTextHover: bool }
static member Default() =
{ usePreviewTextHover = false }

module Extensions =
type JsonRpc with
member jsonRpc.SetOptionsAsync (options: Options) =
async {
do! jsonRpc.InvokeAsync(OptionsSet, options) |> Async.AwaitTask
}
36 changes: 25 additions & 11 deletions src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace FSharp.Compiler.LanguageServer

// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specification. The properties on these
// types are camlCased to match the underlying JSON properties to avoid attributes on every field:
open Newtonsoft.Json.Linq
open Newtonsoft.Json

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

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

type Diagnostic =
{ range: Range
severity: int
severity: int option
code: string
source: string // "F#"
source: string option // "F#"
message: string
relatedInformation: DiagnosticRelatedInformation[] }
relatedInformation: DiagnosticRelatedInformation[] option }
static member Error = 1
static member Warning = 2
static member Information = 3
Expand All @@ -41,9 +44,23 @@ type PublishDiagnosticsParams =
{ uri: DocumentUri
diagnostics: Diagnostic[] }

type InitializeParams = string // TODO:
type ClientCapabilities =
{ workspace: JToken option // TODO: WorkspaceClientCapabilities
textDocument: JToken option // TODO: TextDocumentCapabilities
experimental: JToken option
supportsVisualStudioExtensions: bool option }

[<JsonConverter(typeof<JsonDUConverter>)>]
type Trace =
| Off
| Messages
| Verbose

// Note, this type has many more optional values that can be expanded as support is added.
type WorkspaceFolder =
{ uri: DocumentUri
name: string }

/// Note, this type has many more optional values that can be expanded as support is added.
type ServerCapabilities =
{ hoverProvider: bool }
static member DefaultCapabilities() =
Expand All @@ -52,6 +69,7 @@ type ServerCapabilities =
type InitializeResult =
{ capabilities: ServerCapabilities }

[<JsonConverter(typeof<JsonDUConverter>)>]
type MarkupKind =
| PlainText
| Markdown
Expand All @@ -66,7 +84,3 @@ type Hover =

type TextDocumentIdentifier =
{ uri: DocumentUri }

type TextDocumentPositionParams =
{ textDocument: TextDocumentIdentifier
position: Position }
61 changes: 53 additions & 8 deletions src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,66 @@

namespace FSharp.Compiler.LanguageServer

open System
open System.Runtime.InteropServices
open System.Threading
open Newtonsoft.Json.Linq
open StreamJsonRpc

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

/// Helper to run Async<'T> with a CancellationToken.
let runAsync (cancellationToken: CancellationToken) (computation: Async<'T>) = Async.StartAsTask(computation, cancellationToken=cancellationToken)

member __.State = state

//--------------------------------------------------------------------------
// official LSP methods
//--------------------------------------------------------------------------

[<JsonRpcMethod("initialize")>]
member __.Initialize (args: InitializeParams) =
async {
// note, it's important that this method is `async` because unit tests can then properly verify that the
// JSON RPC handling of async methods is correct
return ServerCapabilities.DefaultCapabilities()
} |> Async.StartAsTask
member __.Initialize
(
processId: Nullable<int>,
[<Optional; DefaultParameterValue(null: string)>] rootPath: string,
[<Optional; DefaultParameterValue(null: string)>] rootUri: DocumentUri,
[<Optional; DefaultParameterValue(null: JToken)>] initializationOptions: JToken,
capabilities: ClientCapabilities,
[<Optional; DefaultParameterValue(null: string)>] trace: string,
[<Optional; DefaultParameterValue(null: WorkspaceFolder[])>] workspaceFolders: WorkspaceFolder[]
) =
{ InitializeResult.capabilities = ServerCapabilities.DefaultCapabilities() }

[<JsonRpcMethod("initialized")>]
member __.Initialized () = ()

[<JsonRpcMethod("shutdown")>]
member __.Shutdown() = state.DoShutdown()
member __.Shutdown(): obj = state.DoShutdown(); null

[<JsonRpcMethod("exit")>]
member __.Exit() = state.DoExit()

[<JsonRpcMethod("$/cancelRequest")>]
member __.cancelRequest (id: JToken) = state.DoCancel()

[<JsonRpcMethod("textDocument/hover")>]
member __.TextDocumentHover (args: TextDocumentPositionParams) = TextDocument.Hover(state, args) |> Async.StartAsTask
member __.TextDocumentHover
(
textDocument: TextDocumentIdentifier,
position: Position,
[<Optional; DefaultParameterValue(CancellationToken())>] cancellationToken: CancellationToken
) =
TextDocument.Hover state textDocument position |> runAsync cancellationToken

//--------------------------------------------------------------------------
// unofficial LSP methods that we implement separately
//--------------------------------------------------------------------------

[<JsonRpcMethod(OptionsSet)>]
member __.OptionsSet
(
options: Options
) =
sprintf "got options %A" options |> Console.Error.WriteLine
state.Options <- options
7 changes: 6 additions & 1 deletion src/fsharp/FSharp.Compiler.LanguageServer/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ open StreamJsonRpc

type Server(sendingStream: Stream, receivingStream: Stream) =

let formatter = JsonMessageFormatter()
let converter = JsonOptionConverter() // special handler to convert between `Option<'T>` and `obj/null`.
do formatter.JsonSerializer.Converters.Add(converter)
let handler = new HeaderDelimitedMessageHandler(sendingStream, receivingStream, formatter)
let state = State()
let methods = Methods(state)
let rpc = new JsonRpc(sendingStream, receivingStream, methods)
let rpc = new JsonRpc(handler, methods)

member __.StartListening() =
rpc.StartListening()

member __.WaitForExitAsync() =
async {
do! Async.AwaitEvent (state.Shutdown)
do! Async.AwaitEvent (state.Exit)
}

interface IDisposable with
Expand Down
14 changes: 14 additions & 0 deletions src/fsharp/FSharp.Compiler.LanguageServer/State.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,22 @@ namespace FSharp.Compiler.LanguageServer
type State() =

let shutdownEvent = new Event<_>()
let exitEvent = new Event<_>()
let cancelEvent = new Event<_>()

[<CLIEvent>]
member __.Shutdown = shutdownEvent.Publish

[<CLIEvent>]
member __.Exit = exitEvent.Publish

[<CLIEvent>]
member __.Cancel = cancelEvent.Publish

member __.DoShutdown() = shutdownEvent.Trigger()

member __.DoExit() = exitEvent.Trigger()

member __.DoCancel() = cancelEvent.Trigger()

member val Options = Options.Default() with get, set
Loading