Skip to content

Commit

Permalink
feat: Extract CSharpLanguageServer.State.ServerDiagnosticPush
Browse files Browse the repository at this point in the history
  • Loading branch information
razzmatazz committed Jul 2, 2024
1 parent 45f3dac commit bc68aa2
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 168 deletions.
1 change: 1 addition & 0 deletions src/CSharpLanguageServer/CSharpLanguageServer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="RoslynHelpers.fs" />
<Compile Include="DocumentationUtil.fs" />
<Compile Include="Lsp/Client.fs" />
<Compile Include="State/ServerDiagnosticPush.fs" />
<Compile Include="State/ServerState.fs" />
<Compile Include="State/ServerRequestContext.fs" />
<Compile Include="Handlers/CSharpMetadata.fs" />
Expand Down
9 changes: 5 additions & 4 deletions src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<LspResult<unit>> =
Expand Down Expand Up @@ -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<LspResult<unit>> =
Expand Down Expand Up @@ -190,7 +191,7 @@ module TextDocumentSync =
return Result.Ok()
}

let didClose (diagnosticsPost: DiagnosticsEvent -> unit)
let didClose (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(closeParams: DidCloseTextDocumentParams)
: Async<LspResult<unit>> =
Expand All @@ -206,7 +207,7 @@ module TextDocumentSync =
return LspResult.notImplemented<TextEdit [] option>
}

let didSave (diagnosticsPost: DiagnosticsEvent -> unit)
let didSave (diagnosticsPost: PushDiagnosticEvent -> unit)
(context: ServerRequestContext)
(saveParams: DidSaveTextDocumentParams)
: Async<LspResult<unit>> =
Expand Down
3 changes: 2 additions & 1 deletion src/CSharpLanguageServer/Handlers/Workspace.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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<LspResult<unit>> = async {
Expand Down
3 changes: 2 additions & 1 deletion src/CSharpLanguageServer/Lsp/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -41,7 +42,7 @@ type CSharpLspServer(
stateActor.PostAndAsyncReply(fun rc -> GetDocumentOfTypeForUri (docType, uri, rc))

let diagnostics = MailboxProcessor.Start(
diagnosticsEventLoop
pushDiagnosticEventLoop
lspClient
getDocumentForUriFromCurrentState)

Expand Down
154 changes: 154 additions & 0 deletions src/CSharpLanguageServer/State/ServerDiagnosticPush.fs
Original file line number Diff line number Diff line change
@@ -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<string, DateTime>
LastPendingDiagnosticsProcessing: DateTime
}

let emptyPushDiagnosticState = {
DocumentBacklog = []
DocumentChanges = Map.empty
LastPendingDiagnosticsProcessing = DateTime.MinValue
}

let processPushDiagnosticEvent
(publishDiagnostics: string -> Diagnostic[] -> Async<unit>)
(getDocumentForUri: string -> Async<Document option>)
(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<PushDiagnosticEvent>) =
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
Loading

0 comments on commit bc68aa2

Please sign in to comment.