diff --git a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
index 37abcdb6..f41cb068 100644
--- a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
+++ b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
@@ -29,6 +29,7 @@
+
diff --git a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
index 2b6e1cfd..07efb59f 100644
--- a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
+++ b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
@@ -11,6 +11,7 @@ open CSharpLanguageServer
open CSharpLanguageServer.Conversions
open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
+open CSharpLanguageServer.State.ServerDiagnosticPush
open CSharpLanguageServer.Types
open CSharpLanguageServer.RoslynHelpers
open CSharpLanguageServer.Logging
@@ -107,7 +108,7 @@ module TextDocumentSync =
let willSaveWaitUntilRegistration (_clientCapabilities: ClientCapabilities) : Registration option = None
- let didOpen (diagnosticsPost: DiagnosticsEvent -> unit)
+ let didOpen (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(openParams: DidOpenTextDocumentParams)
: Async> =
@@ -159,7 +160,7 @@ module TextDocumentSync =
| None ->
LspResult.Ok() |> async.Return
- let didChange (diagnosticsPost: DiagnosticsEvent -> unit)
+ let didChange (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(changeParams: DidChangeTextDocumentParams)
: Async> =
@@ -190,7 +191,7 @@ module TextDocumentSync =
return Result.Ok()
}
- let didClose (diagnosticsPost: DiagnosticsEvent -> unit)
+ let didClose (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(closeParams: DidCloseTextDocumentParams)
: Async> =
@@ -206,7 +207,7 @@ module TextDocumentSync =
return LspResult.notImplemented
}
- let didSave (diagnosticsPost: DiagnosticsEvent -> unit)
+ let didSave (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(saveParams: DidSaveTextDocumentParams)
: Async> =
diff --git a/src/CSharpLanguageServer/Handlers/Workspace.fs b/src/CSharpLanguageServer/Handlers/Workspace.fs
index 2d87d1ff..f66c9b5c 100644
--- a/src/CSharpLanguageServer/Handlers/Workspace.fs
+++ b/src/CSharpLanguageServer/Handlers/Workspace.fs
@@ -10,6 +10,7 @@ open Microsoft.CodeAnalysis.Text
open CSharpLanguageServer
open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
+open CSharpLanguageServer.State.ServerDiagnosticPush
open CSharpLanguageServer.RoslynHelpers
open CSharpLanguageServer.Logging
@@ -77,7 +78,7 @@ module Workspace =
diagnosticsPost(DocumentRemoval uri)
| None -> ()
- let didChangeWatchedFiles (diagnosticsPost: DiagnosticsEvent -> unit)
+ let didChangeWatchedFiles (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(p: DidChangeWatchedFilesParams)
: Async> = async {
diff --git a/src/CSharpLanguageServer/Lsp/Server.fs b/src/CSharpLanguageServer/Lsp/Server.fs
index cd70668a..8d9a356d 100644
--- a/src/CSharpLanguageServer/Lsp/Server.fs
+++ b/src/CSharpLanguageServer/Lsp/Server.fs
@@ -14,6 +14,7 @@ open CSharpLanguageServer.Handlers
open CSharpLanguageServer.Logging
open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
+open CSharpLanguageServer.State.ServerDiagnosticPush
open CSharpLanguageServer.Util
module LspUtils =
@@ -41,7 +42,7 @@ type CSharpLspServer(
stateActor.PostAndAsyncReply(fun rc -> GetDocumentOfTypeForUri (docType, uri, rc))
let diagnostics = MailboxProcessor.Start(
- diagnosticsEventLoop
+ pushDiagnosticEventLoop
lspClient
getDocumentForUriFromCurrentState)
diff --git a/src/CSharpLanguageServer/State/ServerDiagnosticPush.fs b/src/CSharpLanguageServer/State/ServerDiagnosticPush.fs
new file mode 100644
index 00000000..bc3d3957
--- /dev/null
+++ b/src/CSharpLanguageServer/State/ServerDiagnosticPush.fs
@@ -0,0 +1,154 @@
+module CSharpLanguageServer.State.ServerDiagnosticPush
+
+open System
+
+open Microsoft.CodeAnalysis
+open Ionide.LanguageServerProtocol.Types
+
+open CSharpLanguageServer.Conversions
+open CSharpLanguageServer.Logging
+open CSharpLanguageServer.Lsp
+open CSharpLanguageServer.Types
+
+type PushDiagnosticEvent =
+ | DocumentOpenOrChange of string * DateTime
+ | DocumentClose of string
+ | ProcessPendingDiagnostics
+ | DocumentBacklogUpdate
+ | DocumentRemoval of string
+
+type PushDiagnosticState = {
+ DocumentBacklog: string list
+ DocumentChanges: Map
+ LastPendingDiagnosticsProcessing: DateTime
+}
+
+let emptyPushDiagnosticState = {
+ DocumentBacklog = []
+ DocumentChanges = Map.empty
+ LastPendingDiagnosticsProcessing = DateTime.MinValue
+}
+
+let processPushDiagnosticEvent
+ (publishDiagnostics: string -> Diagnostic[] -> Async)
+ (getDocumentForUri: string -> Async)
+ (state: PushDiagnosticState)
+ (currentEventQueueLength: int)
+ msg =
+ match msg with
+ | DocumentRemoval uri -> async {
+ let updatedDocumentChanges = state.DocumentChanges |> Map.remove uri
+ let newState = { state with DocumentChanges = updatedDocumentChanges }
+ return newState, [ DocumentBacklogUpdate ]
+ }
+
+ | DocumentOpenOrChange (uri, timestamp) -> async {
+ let newDocChanges = state.DocumentChanges |> Map.add uri timestamp
+ let newState = { state with DocumentChanges = newDocChanges }
+ return newState, [ DocumentBacklogUpdate ]
+ }
+
+ | DocumentBacklogUpdate -> async {
+ // here we build new backlog for background diagnostics processing
+ // which will consider documents by their last modification date
+ // for processing first
+ let newBacklog =
+ state.DocumentChanges
+ |> Seq.sortByDescending (fun kv -> kv.Value)
+ |> Seq.map (fun kv -> kv.Key)
+ |> List.ofSeq
+
+ let newState = { state with DocumentBacklog = newBacklog }
+ return newState, []
+ }
+
+ | DocumentClose uri -> async {
+ let prunedBacklog = state.DocumentBacklog
+ |> Seq.filter (fun x -> x <> uri)
+ |> List.ofSeq
+
+ let prunedDocumentChanges = state.DocumentChanges |> Map.remove uri
+
+ let newState = { state with DocumentBacklog = prunedBacklog
+ DocumentChanges = prunedDocumentChanges }
+ return newState, []
+ }
+
+ | ProcessPendingDiagnostics ->
+ let doProcessPendingDiagnostics = async {
+ let docUriMaybe, newBacklog =
+ match state.DocumentBacklog with
+ | [] -> (None, [])
+ | uri :: remainder -> (Some uri, remainder)
+
+ match docUriMaybe with
+ | Some docUri ->
+ let! docAndTypeMaybe = getDocumentForUri docUri
+
+ match docAndTypeMaybe with
+ | Some doc ->
+ let! ct = Async.CancellationToken
+ let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
+ match semanticModelMaybe |> Option.ofObj with
+ | Some semanticModel ->
+ let diagnostics =
+ semanticModel.GetDiagnostics()
+ |> Seq.map Diagnostic.fromRoslynDiagnostic
+ |> Array.ofSeq
+
+ do! publishDiagnostics docUri diagnostics
+ | None -> ()
+ | None -> ()
+ | None -> ()
+
+ let newState = { state with DocumentBacklog = newBacklog
+ LastPendingDiagnosticsProcessing = DateTime.Now }
+
+ let eventsToPost = match newBacklog with
+ | [] -> []
+ | _ -> [ ProcessPendingDiagnostics ]
+
+ return newState, eventsToPost
+ }
+
+ let timeSinceLastProcessing =
+ (DateTime.Now - state.LastPendingDiagnosticsProcessing)
+
+ if timeSinceLastProcessing > TimeSpan.FromMilliseconds(250) || currentEventQueueLength = 0 then
+ doProcessPendingDiagnostics
+ else
+ async { return state, [] }
+
+let pushDiagnosticEventLoop
+ (lspClient: CSharpLspClient)
+ getDocumentForUriFromCurrentState
+ (inbox: MailboxProcessor) =
+ let logger = LogProvider.getLoggerByName "ServerDiagnosticPush.pushDiagnosticEventLoop"
+
+ let rec loop state = async {
+ try
+ let! msg = inbox.Receive()
+ let! (newState, eventsToPost) =
+ processPushDiagnosticEvent
+ // TODO: can we provide value for PublishDiagnosticsParams.Version?
+ (fun docUri diagnostics -> lspClient.TextDocumentPublishDiagnostics { Uri = docUri;
+ Version = None;
+ Diagnostics = diagnostics;
+ })
+ (getDocumentForUriFromCurrentState AnyDocument)
+ state
+ inbox.CurrentQueueLength
+ msg
+
+ for ev in eventsToPost do inbox.Post(ev)
+
+ return! loop newState
+ with
+ | ex ->
+ logger.warn (
+ Log.setMessage "unhandled exception in `diagnostics`: {message}"
+ >> Log.addContext "message" (string ex))
+ raise ex
+ }
+
+ loop emptyPushDiagnosticState
diff --git a/src/CSharpLanguageServer/State/ServerState.fs b/src/CSharpLanguageServer/State/ServerState.fs
index fa5653dd..b889d0bc 100644
--- a/src/CSharpLanguageServer/State/ServerState.fs
+++ b/src/CSharpLanguageServer/State/ServerState.fs
@@ -10,9 +10,8 @@ open Ionide.LanguageServerProtocol
open CSharpLanguageServer.RoslynHelpers
open CSharpLanguageServer.Types
-open CSharpLanguageServer.Lsp
open CSharpLanguageServer.Logging
-open CSharpLanguageServer.Conversions
+open CSharpLanguageServer.State.ServerDiagnosticPush
type DecompiledMetadataDocument = {
Metadata: CSharpMetadataInformation
@@ -42,6 +41,7 @@ and ServerState = {
RunningRequests: Map
RequestQueue: ServerRequest list
SolutionReloadPending: DateTime option
+ PushDiagnostics: MailboxProcessor option
}
let pullFirstRequestMaybe requestQueue =
@@ -74,22 +74,19 @@ let pullNextRequestMaybe requestQueue =
(Some nextRequest, queueRemainder)
-let emptyServerState = { Settings = ServerSettings.Default
- RootPath = Directory.GetCurrentDirectory()
- LspClient = None
- ClientCapabilities = emptyClientCapabilities
- Solution = None
- OpenDocVersions = Map.empty
- DecompiledMetadata = Map.empty
- LastRequestId = 0
- RunningRequests = Map.empty
- RequestQueue = []
- SolutionReloadPending = None }
-
-type ServerDocumentType =
- | UserDocument // user Document from solution, on disk
- | DecompiledDocument // Document decompiled from metadata, readonly
- | AnyDocument
+let emptyServerState =
+ { Settings = ServerSettings.Default
+ RootPath = Directory.GetCurrentDirectory()
+ LspClient = None
+ ClientCapabilities = emptyClientCapabilities
+ Solution = None
+ OpenDocVersions = Map.empty
+ DecompiledMetadata = Map.empty
+ LastRequestId = 0
+ RunningRequests = Map.empty
+ RequestQueue = []
+ SolutionReloadPending = None
+ PushDiagnostics = None }
type ServerStateEvent =
| SettingsChange of ServerSettings
@@ -294,150 +291,6 @@ let serverEventLoop initialState (inbox: MailboxProcessor) =
loop initialState
-
-type DiagnosticsEvent =
- | DocumentOpenOrChange of string * DateTime
- | DocumentClose of string
- | ProcessPendingDiagnostics
- | DocumentBacklogUpdate
- | DocumentRemoval of string
-
-type DiagnosticsState = {
- DocumentBacklog: string list
- DocumentChanges: Map
- LastPendingDiagnosticsProcessing: DateTime
-}
-
-let emptyDiagnosticsState = {
- DocumentBacklog = []
- DocumentChanges = Map.empty
- LastPendingDiagnosticsProcessing = DateTime.MinValue
-}
-
-let processDiagnosticsEvent
- (publishDiagnostics: string -> Diagnostic[] -> Async)
- (getDocumentForUri: string -> Async)
- (state: DiagnosticsState)
- (currentEventQueueLength: int)
- msg =
- match msg with
- | DocumentRemoval uri -> async {
- let updatedDocumentChanges = state.DocumentChanges |> Map.remove uri
- let newState = { state with DocumentChanges = updatedDocumentChanges }
- return newState, [ DocumentBacklogUpdate ]
- }
-
- | DocumentOpenOrChange (uri, timestamp) -> async {
- let newDocChanges = state.DocumentChanges |> Map.add uri timestamp
- let newState = { state with DocumentChanges = newDocChanges }
- return newState, [ DocumentBacklogUpdate ]
- }
-
- | DocumentBacklogUpdate -> async {
- // here we build new backlog for background diagnostics processing
- // which will consider documents by their last modification date
- // for processing first
- let newBacklog =
- state.DocumentChanges
- |> Seq.sortByDescending (fun kv -> kv.Value)
- |> Seq.map (fun kv -> kv.Key)
- |> List.ofSeq
-
- let newState = { state with DocumentBacklog = newBacklog }
- return newState, []
- }
-
- | DocumentClose uri -> async {
- let prunedBacklog = state.DocumentBacklog
- |> Seq.filter (fun x -> x <> uri)
- |> List.ofSeq
-
- let prunedDocumentChanges = state.DocumentChanges |> Map.remove uri
-
- let newState = { state with DocumentBacklog = prunedBacklog
- DocumentChanges = prunedDocumentChanges }
- return newState, []
- }
-
- | ProcessPendingDiagnostics ->
- let doProcessPendingDiagnostics = async {
- let docUriMaybe, newBacklog =
- match state.DocumentBacklog with
- | [] -> (None, [])
- | uri :: remainder -> (Some uri, remainder)
-
- match docUriMaybe with
- | Some docUri ->
- let! docAndTypeMaybe = getDocumentForUri docUri
-
- match docAndTypeMaybe with
- | Some doc ->
- let! ct = Async.CancellationToken
- let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
- match semanticModelMaybe |> Option.ofObj with
- | Some semanticModel ->
- let diagnostics =
- semanticModel.GetDiagnostics()
- |> Seq.map Diagnostic.fromRoslynDiagnostic
- |> Array.ofSeq
-
- do! publishDiagnostics docUri diagnostics
- | None -> ()
- | None -> ()
- | None -> ()
-
- let newState = { state with DocumentBacklog = newBacklog
- LastPendingDiagnosticsProcessing = DateTime.Now }
-
- let eventsToPost = match newBacklog with
- | [] -> []
- | _ -> [ ProcessPendingDiagnostics ]
-
- return newState, eventsToPost
- }
-
- let timeSinceLastProcessing =
- (DateTime.Now - state.LastPendingDiagnosticsProcessing)
-
- if timeSinceLastProcessing > TimeSpan.FromMilliseconds(250) || currentEventQueueLength = 0 then
- doProcessPendingDiagnostics
- else
- async { return state, [] }
-
-let diagnosticsEventLoop
- (lspClient: CSharpLspClient)
- getDocumentForUriFromCurrentState
- (inbox: MailboxProcessor) =
- let logger = LogProvider.getLoggerByName "DiagnosticsEventLoop"
-
- let rec loop state = async {
- try
- let! msg = inbox.Receive()
- let! (newState, eventsToPost) =
- processDiagnosticsEvent
- // TODO: can we provide value for PublishDiagnosticsParams.Version?
- (fun docUri diagnostics -> lspClient.TextDocumentPublishDiagnostics { Uri = docUri;
- Version = None;
- Diagnostics = diagnostics;
- })
- (getDocumentForUriFromCurrentState AnyDocument)
- state
- inbox.CurrentQueueLength
- msg
-
- for ev in eventsToPost do inbox.Post(ev)
-
- return! loop newState
- with
- | ex ->
- logger.warn (
- Log.setMessage "unhandled exception in `diagnostics`: {message}"
- >> Log.addContext "message" (string ex))
- raise ex
- }
-
- loop emptyDiagnosticsState
-
type ServerSettingsDto = {
csharp: ServerSettingsCSharpDto option
}
diff --git a/src/CSharpLanguageServer/Types.fs b/src/CSharpLanguageServer/Types.fs
index f2ca18f2..bbf9d184 100644
--- a/src/CSharpLanguageServer/Types.fs
+++ b/src/CSharpLanguageServer/Types.fs
@@ -54,3 +54,8 @@ let emptyClientCapabilities: ClientCapabilities =
General = None
Experimental = None
}
+
+type ServerDocumentType =
+ | UserDocument // user Document from solution, on disk
+ | DecompiledDocument // Document decompiled from metadata, readonly
+ | AnyDocument