-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Extract CSharpLanguageServer.State.ServerDiagnosticPush
- Loading branch information
1 parent
45f3dac
commit bc68aa2
Showing
7 changed files
with
184 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.