Skip to content
Open
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
89 changes: 89 additions & 0 deletions src/AotJsonRpc.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module Ionide.LanguageServerProtocol.JsonRpc

open System
open System.Threading
open System.Threading.Tasks
open Ionide.LanguageServerProtocol.Types
open StreamJsonRpc

module ErrorCodes =
let jsonrpcReservedErrorRangeStart = -32099
let jsonrpcReservedErrorRangeEnd = -32000
let lspReservedErrorRangeStart = -32899
let lspReservedErrorRangeEnd = -32899

type Error = {
Code: int
Message: string
Data: LSPAny option
} with

static member Create(code: int, message: string) = { Code = code; Message = message; Data = None }

static member ParseError(?message) = Error.Create(int Types.ErrorCodes.ParseError, defaultArg message "Parse error")

static member InvalidRequest(?message) =
Error.Create(int Types.ErrorCodes.InvalidRequest, defaultArg message "Invalid Request")

static member MethodNotFound(?message) =
Error.Create(int Types.ErrorCodes.MethodNotFound, defaultArg message "Method not found")

static member InvalidParams(?message) =
Error.Create(int Types.ErrorCodes.InvalidParams, defaultArg message "Invalid params")

static member InternalError(?message: string) =
Error.Create(int Types.ErrorCodes.InternalError, defaultArg message "Internal error")

static member RequestCancelled(?message) =
Error.Create(int LSPErrorCodes.RequestCancelled, defaultArg message "Request cancelled")

type LspResult<'result> = Result<'result, Error>
type AsyncLspResult<'result> = Async<LspResult<'result>>

module LspResult =
let success x : LspResult<_> = Ok x
let invalidParams message : LspResult<_> = Error(Error.InvalidParams message)

let internalError<'a> (message: string) : LspResult<'a> =
Error(Error.Create(int Types.ErrorCodes.InvalidParams, message))

let notImplemented<'a> : LspResult<'a> = Error(Error.MethodNotFound())
let requestCancelled<'a> : LspResult<'a> = Error(Error.RequestCancelled())

module AsyncLspResult =
let success x : AsyncLspResult<_> = async.Return(Ok x)
let invalidParams message : AsyncLspResult<_> = async.Return(LspResult.invalidParams message)
let internalError message : AsyncLspResult<_> = async.Return(LspResult.internalError message)
let notImplemented<'a> : AsyncLspResult<'a> = async.Return LspResult.notImplemented
let requestCancelled<'a> : AsyncLspResult<'a> = async.Return LspResult.requestCancelled

module Requests =
let requestHandling<'param, 'result> (run: 'param -> AsyncLspResult<'result>) : Delegate =
let runAsTask param ct =
let pending = run param

async {
let! result = pending

match result with
| Ok value -> return value
| Error error ->
let rpcException = LocalRpcException(error.Message)
rpcException.ErrorCode <- error.Code

rpcException.ErrorData <-
error.Data
|> Option.map box
|> Option.defaultValue null

return raise rpcException
}
|> fun operation -> Async.StartAsTask(operation, cancellationToken = ct)

Func<'param, CancellationToken, Task<'result>>(runAsTask) :> Delegate

let internal notificationSuccess (response: Async<unit>) =
async {
do! response
return Ok()
}
138 changes: 138 additions & 0 deletions src/AotLanguageServerProtocol.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
namespace Ionide.LanguageServerProtocol

module Server =
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Ionide.LanguageServerProtocol.JsonRpc
open Ionide.LanguageServerProtocol.Logging
open Ionide.LanguageServerProtocol.StaticMetadata
open StreamJsonRpc
open StreamJsonRpc.Protocol

type ClientNotificationSender = string -> obj -> AsyncLspResult<unit>

type ClientRequestSender =
abstract member Send<'a> : string -> obj -> AsyncLspResult<'a>

let logger = LogProvider.getLoggerByName "LSP Server"

type LspCloseReason =
| RequestedByClient = 0
| ErrorExitWithoutShutdown = 1
| ErrorStreamClosed = 2

let requestHandling<'param, 'result> (run: 'param -> AsyncLspResult<'result>) = Requests.requestHandling run

let serverRequestHandling<'server, 'param, 'result when 'server :> ILspServer>
(run: 'server -> 'param -> AsyncLspResult<'result>)
: Mappings.ServerRequestHandling<'server> =
{ Run = fun server -> requestHandling (run server) }

let defaultRequestHandlings () : Map<string, Mappings.ServerRequestHandling<'server>> =
Mappings.routeMappings ()
|> Map.ofList

type private StaticProtocolRpc(handler: IJsonRpcMessageHandler) =
inherit JsonRpc(handler)

override _.IsFatalException(exception': Exception) =
match exception' with
| :? LocalRpcException
| :? System.Text.Json.JsonException -> false
| _ -> true

override this.CreateErrorDetails(request, exception') =
match exception' with
| :? System.Text.Json.JsonException as jsonException ->
JsonRpcError.ErrorDetail(Code = JsonRpcErrorCode.ParseError, Message = jsonException.Message)
| _ -> base.CreateErrorDetails(request, exception')

let private run<'client, 'server when 'client :> ILspClient and 'server :> ILspServer>
(requestHandlings: Map<string, Mappings.ServerRequestHandling<'server>>)
(handler: IJsonRpcMessageHandler)
(clientCreator: (ClientNotificationSender * ClientRequestSender) -> 'client)
(serverCreator: 'client -> 'server)
=
use jsonRpc = new StaticProtocolRpc(handler)

let sendNotification methodName (value: obj) =
async {
do!
ProtocolMetadata.NotifyAsync(jsonRpc, methodName, ProtocolMetadata.Serialize(value))
|> Async.AwaitTask

return LspResult.success ()
}

let sendRequest methodName (value: obj) =
async {
let! response =
ProtocolMetadata.InvokeAsync(jsonRpc, methodName, ProtocolMetadata.Serialize(value))
|> Async.AwaitTask

return
ProtocolMetadata.Deserialize<'response>(response)
|> LspResult.success
}

use client =
clientCreator (
sendNotification,
{ new ClientRequestSender with
member _.Send methodName value = sendRequest methodName value
}
)

use server = serverCreator client
let mutable shutdownReceived = false
let mutable exitReceived = false
use exitSemaphore = new SemaphoreSlim(0, 1)

let target =
StaticProtocolTarget(
server,
requestHandlings,
Action(fun () -> shutdownReceived <- true),
Action(fun () ->
exitReceived <- true

exitSemaphore.Release()
|> ignore
)
)

jsonRpc.AddLocalRpcTarget(ProtocolMetadata.Target, target, null)
jsonRpc.StartListening()
let completed = Task.WaitAny(jsonRpc.Completion, exitSemaphore.WaitAsync())

if
completed = 0
&& not jsonRpc.Completion.IsCompletedSuccessfully
then
jsonRpc.Completion.GetAwaiter().GetResult()

match shutdownReceived, exitReceived with
| true, true -> LspCloseReason.RequestedByClient
| false, true -> LspCloseReason.ErrorExitWithoutShutdown
| _ -> LspCloseReason.ErrorStreamClosed

let start<'client, 'server when 'client :> ILspClient and 'server :> ILspServer>
(requestHandlings: Map<string, Mappings.ServerRequestHandling<'server>>)
(input: Stream)
(output: Stream)
(clientCreator: (ClientNotificationSender * ClientRequestSender) -> 'client)
(serverCreator: 'client -> 'server)
=
use handler = new HeaderDelimitedMessageHandler(output, input, FormatterFactory.Create())
run requestHandlings handler clientCreator serverCreator

let startWs<'client, 'server when 'client :> ILspClient and 'server :> ILspServer>
(requestHandlings: Map<string, Mappings.ServerRequestHandling<'server>>)
(socket: Net.WebSockets.WebSocket)
(clientCreator: (ClientNotificationSender * ClientRequestSender) -> 'client)
(serverCreator: 'client -> 'server)
=
use handler = new WebSocketMessageHandler(socket, FormatterFactory.Create())
run requestHandlings handler clientCreator serverCreator
88 changes: 88 additions & 0 deletions src/AotRuntime.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace Ionide.LanguageServerProtocol

module internal AotRuntime =
open System
open System.Text.Json
open System.Threading
open System.Threading.Tasks
open Ionide.LanguageServerProtocol.JsonRpc
open Ionide.LanguageServerProtocol.StaticMetadata
open StreamJsonRpc

let private findHandling server (handlings: Map<string, Mappings.ServerRequestHandling<'server>>) route =
match Map.tryFind route handlings with
| Some handling -> handling.Run server
| None ->
let rpcException = LocalRpcException(String.Concat("Method not found: ", route))
rpcException.ErrorCode <- int Types.ErrorCodes.MethodNotFound
raise rpcException

let invokeWithParameter<'server, 'parameter, 'result when 'server :> ILspServer>
(server: 'server)
(handlings: Map<string, Mappings.ServerRequestHandling<'server>>)
route
(request: JsonElement)
(cancellationToken: CancellationToken)
(_infer: 'parameter -> AsyncLspResult<'result>)
=
task {
let handling = findHandling server handlings route :?> Func<'parameter, CancellationToken, Task<'result>>
let parameter = ProtocolMetadata.Deserialize<'parameter>(request)
let! result = handling.Invoke(parameter, cancellationToken)
return ProtocolMetadata.Serialize(result)
}

let invokeWithoutParameter<'server, 'result when 'server :> ILspServer>
(server: 'server)
(handlings: Map<string, Mappings.ServerRequestHandling<'server>>)
route
(cancellationToken: CancellationToken)
(_infer: unit -> AsyncLspResult<'result>)
=
task {
let handling = findHandling server handlings route :?> Func<unit, CancellationToken, Task<'result>>
let! result = handling.Invoke((), cancellationToken)
return ProtocolMetadata.Serialize(result)
}

let notifyWithParameter<'server, 'parameter when 'server :> ILspServer>
(server: 'server)
(handlings: Map<string, Mappings.ServerRequestHandling<'server>>)
route
(request: JsonElement)
(cancellationToken: CancellationToken)
(_infer: 'parameter -> Async<unit>)
: Task =
invokeWithParameter
server
handlings
route
request
cancellationToken
(fun parameter ->
async {
do! _infer parameter
return Ok()
}
)
:> Task

let notifyWithoutParameter<'server when 'server :> ILspServer>
(server: 'server)
(handlings: Map<string, Mappings.ServerRequestHandling<'server>>)
route
(cancellationToken: CancellationToken)
(_infer: unit -> Async<unit>)
: Task =
invokeWithoutParameter
server
handlings
route
cancellationToken
(fun () ->
async {
do! _infer ()
return Ok()
}
)
:> Task
56 changes: 56 additions & 0 deletions src/AotTypes.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Ionide.LanguageServerProtocol.Types

open System
open System.Text.Json

/// Marks the fixed discriminator value of a protocol record used in an erased union.
type UnionKindAttribute(value: string) =
inherit Attribute()
member _.Value = value

/// Marks a protocol union whose case wrapper is erased on the JSON wire.
type ErasedUnionAttribute() =
inherit Attribute()

[<ErasedUnion; System.Diagnostics.DebuggerDisplay("U2")>]
type U2<'T1, 'T2> =
| C1 of 'T1
| C2 of 'T2

[<ErasedUnion; System.Diagnostics.DebuggerDisplay("U3")>]
type U3<'T1, 'T2, 'T3> =
| C1 of 'T1
| C2 of 'T2
| C3 of 'T3

[<ErasedUnion; System.Diagnostics.DebuggerDisplay("U4")>]
type U4<'T1, 'T2, 'T3, 'T4> =
| C1 of 'T1
| C2 of 'T2
| C3 of 'T3
| C4 of 'T4

/// A JSON value carried in an LSP `any` slot.
[<Sealed>]
type LSPAny private (element: JsonElement) =
let element = element.Clone()

/// The underlying System.Text.Json value.
member _.JsonElement = element

override _.ToString() = element.GetRawText()

override _.GetHashCode() = StringComparer.Ordinal.GetHashCode(element.GetRawText())

override _.Equals(obj: obj) =
match obj with
| :? LSPAny as value -> StringComparer.Ordinal.Equals(element.GetRawText(), value.JsonElement.GetRawText())
| _ -> false

interface IEquatable<LSPAny> with
member _.Equals(other) =
not (obj.ReferenceEquals(other, null))
&& StringComparer.Ordinal.Equals(element.GetRawText(), other.JsonElement.GetRawText())

/// Wraps a System.Text.Json value without retaining its owning JsonDocument.
static member fromJsonElement(element: JsonElement) = LSPAny(element)
7 changes: 6 additions & 1 deletion src/ClientServer.cg.fs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,12 @@ type ILspClient =
abstract WorkspaceApplyEdit: ApplyWorkspaceEditParams -> AsyncLspResult<ApplyWorkspaceEditResult>

module Mappings =
type ServerRequestHandling<'server when 'server :> ILspServer> = { Run: 'server -> System.Delegate }
type ServerRequestHandling<'server when 'server :> ILspServer> =
{ Run: 'server -> System.Delegate }
#if NET10_0

override _.ToString() = "ServerRequestHandling"
#endif

let routeMappings () =
let serverRequestHandling run = {
Expand Down
Loading