-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathLanguageServer.fs
More file actions
357 lines (288 loc) · 14.3 KB
/
LanguageServer.fs
File metadata and controls
357 lines (288 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
module LSP.LanguageServer
open LSP.Log
open System
open System.Threading
open System.IO
open System.Text
open FSharp.Data
open Types
open LSP.Json.Ser
open JsonExtensions
let private jsonWriteOptions =
{ defaultJsonWriteOptions with
customWriters =
[ writeTextDocumentSaveReason
writeFileChangeType
writeTextDocumentSyncKind
writeDiagnosticSeverity
writeTrace
writeInsertTextFormat
writeCompletionItemKind
writeMarkedString
writeDocumentHighlightKind
writeSymbolKind
writeRegisterCapability
writeMessageType
writeMarkupKind
writeHoverContent ] }
let private serializeInitializeResult =
serializerFactory<InitializeResult> jsonWriteOptions
let private serializeTextEditList =
serializerFactory<TextEdit list> jsonWriteOptions
let private serializeCompletionList =
serializerFactory<CompletionList> jsonWriteOptions
let private serializeCompletionListOption = Option.map serializeCompletionList
let private serializeHover = serializerFactory<Hover> jsonWriteOptions
let private serializeHoverOption = Option.map serializeHover
let private serializeCompletionItem =
serializerFactory<CompletionItem> jsonWriteOptions
let private serializeSignatureHelp =
serializerFactory<SignatureHelp> jsonWriteOptions
let private serializeSignatureHelpOption = Option.map serializeSignatureHelp
let private serializeLocationList =
serializerFactory<Location list> jsonWriteOptions
let private serializeDocumentHighlightList =
serializerFactory<DocumentHighlight list> jsonWriteOptions
let private serializeSymbolInformationList =
serializerFactory<SymbolInformation list> jsonWriteOptions
let private serializeDocumentSymbolList =
serializerFactory<DocumentSymbol list> jsonWriteOptions
let private serializeCommandList = serializerFactory<Command list> jsonWriteOptions
let private serializeCodeLensList =
serializerFactory<CodeLens list> jsonWriteOptions
let private serializeCodeLens = serializerFactory<CodeLens> jsonWriteOptions
let private serializeDocumentLinkList =
serializerFactory<DocumentLink list> jsonWriteOptions
let private serializeDocumentLink = serializerFactory<DocumentLink> jsonWriteOptions
let private serializeWorkspaceEdit =
serializerFactory<WorkspaceEdit> jsonWriteOptions
let private serializePublishDiagnostics =
serializerFactory<PublishDiagnosticsParams> jsonWriteOptions
let private serializeShowMessage =
serializerFactory<ShowMessageParams> jsonWriteOptions
let private serializeRegistrationParams =
serializerFactory<RegistrationParams> jsonWriteOptions
let private serializeLoadingBarParams =
serializerFactory<LoadingBarParams> jsonWriteOptions
let private serializeGetWordRangeAtPosition =
serializerFactory<GetWordRangeAtPositionParams> jsonWriteOptions
let private serializeApplyWorkspaceEdit =
serializerFactory<ApplyWorkspaceEditParams> jsonWriteOptions
let private serializeCreateVirtualFileParams =
serializerFactory<CreateVirtualFileParams> jsonWriteOptions
let private serializeLogMessageParams =
serializerFactory<LogMessageParams> jsonWriteOptions
let private serializeExecuteCommandResponse =
serializerFactory<ExecuteCommandResponse> jsonWriteOptions
let private serializeExecuteCommandResponseOption =
Option.map serializeExecuteCommandResponse
let private serializeShutdownResponse =
serializerFactory<int option> jsonWriteOptions
type msg =
| Request of int * AsyncReplyChannel<JsonValue>
| Response of int * JsonValue
let responseAgent =
MailboxProcessor.Start(fun agent ->
let rec loop state =
async {
let! msg = agent.Receive()
match msg with
| Request(id, reply) -> return! loop ((id, reply) :: state)
| Response(id, value) ->
let result = state |> List.tryFind (fun (i, _) -> i = id)
match result with
| Some(_, reply) -> reply.Reply(value)
| None -> eprintfn $"Unexpected response %i{id}"
return! loop (state |> List.filter (fun (i, _) -> i <> id))
}
loop [])
let monitor = Lock()
let private writeClient (client: BinaryWriter, messageText: string) =
let messageBytes = Encoding.UTF8.GetBytes(messageText)
let headerText = $"Content-Length: %d{messageBytes.Length}\r\n\r\n"
let headerBytes = Encoding.UTF8.GetBytes(headerText)
monitor.Enter()
try
client.Write(headerBytes)
client.Write(messageBytes)
finally
monitor.Exit()
let respond (client: BinaryWriter, requestId: int, jsonText: string) =
let messageText = $"""{{"id":%d{requestId},"result":%s{jsonText}}}"""
writeClient (client, messageText)
let private notifyClient (client: BinaryWriter, method: string, jsonText: string) =
let messageText = $"""{{"method":"%s{method}","params":%s{jsonText}}}"""
writeClient (client, messageText)
let private requestClient (client: BinaryWriter, id: int, method: string, jsonText: string) =
async {
let reply =
responseAgent.PostAndAsyncReply(fun replyChannel -> Request(id, replyChannel))
let messageText =
$"""{{"id":%d{id},"method":"%s{method}", "params":%s{jsonText}}}"""
writeClient (client, messageText)
return! reply
}
let private thenMap (f: 'A -> 'B) (result: Async<'A>) : Async<'B> =
async {
let! a = result
return f a
}
let private thenSome = thenMap Some
let private thenNone (result: Async<'A>) : Async<string option> = result |> thenMap (fun _ -> None)
let private notExit (message: Parser.Message) =
match message with
| Parser.NotificationMessage("exit", _) -> false
| _ -> true
let readMessages (receive: BinaryReader) : seq<Parser.Message> =
let tokens = Tokenizer.tokenize receive
let parse = Seq.map Parser.parseMessage tokens
Seq.takeWhile notExit parse
type RealClient(send: BinaryWriter) =
interface ILanguageClient with
member this.LogMessage(p: LogMessageParams) : unit =
let json = serializeLogMessageParams p
notifyClient (send, "window/logMessage", json)
member this.PublishDiagnostics(p: PublishDiagnosticsParams) : unit =
let json = serializePublishDiagnostics p
notifyClient (send, "textDocument/publishDiagnostics", json)
member this.ShowMessage(p: ShowMessageParams) : unit =
let json = serializeShowMessage p
notifyClient (send, "window/showMessage", json)
member this.RegisterCapability(p: RegisterCapability) : unit =
match p with
| RegisterCapability.DidChangeWatchedFiles _ ->
let register =
{ id = Guid.NewGuid().ToString()
method = "workspace/didChangeWatchedFiles"
registerOptions = p }
let message = { registrations = [ register ] }
let json = serializeRegistrationParams message
notifyClient (send, "client/registerCapability", json)
member this.CustomNotification(method: string, json: JsonValue) : unit =
let jsonString = json.ToString(JsonSaveOptions.DisableFormatting)
notifyClient (send, method, jsonString)
member this.ApplyWorkspaceEdit(p: ApplyWorkspaceEditParams) : Async<JsonValue> =
async {
let json = serializeApplyWorkspaceEdit p
let id = Random.Shared.Next()
return! requestClient (send, id, "workspace/applyEdit", json)
}
member this.CustomRequest(method: string, json: string) : Async<JsonValue> =
async {
// let jsonString = json.ToString(JsonSaveOptions.DisableFormatting)
let id = Random.Shared.Next()
return! requestClient (send, id, method, json)
}
type private PendingTask =
| ProcessNotification of method: string * task: Async<unit>
| ProcessRequest of id: int * task: Async<string option> * cancel: CancellationTokenSource
| Quit
let connect (serverFactory: ILanguageClient -> ILanguageServer, receive: BinaryReader, send: BinaryWriter) =
let server = serverFactory (RealClient(send))
let processRequest (request: Request) : Async<string option> =
match request with
| Initialize(p) -> server.Initialize(p) |> thenMap serializeInitializeResult |> thenSome
| Shutdown -> server.Shutdown() |> thenMap serializeShutdownResponse |> thenSome
| WillSaveWaitUntilTextDocument(p) ->
server.WillSaveWaitUntilTextDocument(p)
|> thenMap serializeTextEditList
|> thenSome
| Completion(p) -> server.Completion(p) |> thenMap serializeCompletionListOption
| Hover(p) ->
server.Hover(p)
|> thenMap serializeHoverOption
|> thenMap (Option.defaultValue "null")
|> thenSome
| ResolveCompletionItem(p) -> server.ResolveCompletionItem(p) |> thenMap serializeCompletionItem |> thenSome
| SignatureHelp(p) ->
server.SignatureHelp(p)
|> thenMap serializeSignatureHelpOption
|> thenMap (Option.defaultValue "null")
|> thenSome
| GotoDefinition(p) -> server.GotoDefinition(p) |> thenMap serializeLocationList |> thenSome
| FindReferences(p) -> server.FindReferences(p) |> thenMap serializeLocationList |> thenSome
| DocumentHighlight(p) ->
server.DocumentHighlight(p)
|> thenMap serializeDocumentHighlightList
|> thenSome
| DocumentSymbols(p) -> server.DocumentSymbols(p) |> thenMap serializeDocumentSymbolList |> thenSome
| WorkspaceSymbols(p) -> server.WorkspaceSymbols(p) |> thenMap serializeSymbolInformationList |> thenSome
| CodeActions(p) -> server.CodeActions(p) |> thenMap serializeCommandList |> thenSome
| CodeLens(p) -> server.CodeLens(p) |> thenMap serializeCodeLensList |> thenSome
| ResolveCodeLens(p) -> server.ResolveCodeLens(p) |> thenMap serializeCodeLens |> thenSome
| DocumentLink(p) -> server.DocumentLink(p) |> thenMap serializeDocumentLinkList |> thenSome
| ResolveDocumentLink(p) -> server.ResolveDocumentLink(p) |> thenMap serializeDocumentLink |> thenSome
| DocumentFormatting(p) -> server.DocumentFormatting(p) |> thenMap serializeTextEditList |> thenSome
| DocumentRangeFormatting(p) -> server.DocumentRangeFormatting(p) |> thenMap serializeTextEditList |> thenSome
| DocumentOnTypeFormatting(p) -> server.DocumentOnTypeFormatting(p) |> thenMap serializeTextEditList |> thenSome
| Rename(p) -> server.Rename(p) |> thenMap serializeWorkspaceEdit |> thenSome
| ExecuteCommand(p) -> server.ExecuteCommand p |> thenMap serializeExecuteCommandResponseOption
| DidChangeWorkspaceFolders(p) -> server.DidChangeWorkspaceFolders(p) |> thenNone
let processNotification (n: Notification) =
match n with
| Initialized -> server.Initialized()
| DidChangeConfiguration(p) -> server.DidChangeConfiguration(p)
| DidOpenTextDocument(p) -> server.DidOpenTextDocument(p)
| DidChangeTextDocument(p) -> server.DidChangeTextDocument(p)
| WillSaveTextDocument(p) -> server.WillSaveTextDocument(p)
| DidSaveTextDocument(p) -> server.DidSaveTextDocument(p)
| DidCloseTextDocument(p) -> server.DidCloseTextDocument(p)
| DidChangeWatchedFiles(p) -> server.DidChangeWatchedFiles(p)
| DidFocusFile(p) -> server.DidFocusFile(p)
| OtherNotification _ -> async { () }
// Read messages and process cancellations on a separate thread
let pendingRequests =
System.Collections.Concurrent.ConcurrentDictionary<int, CancellationTokenSource>()
let processQueue =
new System.Collections.Concurrent.BlockingCollection<PendingTask>(10)
Thread(fun () ->
try
// Read all messages on the main thread
for m in readMessages receive do
// Process cancellations immediately
match m with
| Parser.NotificationMessage("$/cancelRequest", Some json) ->
let id = json?id.AsInteger()
let stillRunning, pendingRequest = pendingRequests.TryGetValue(id)
if stillRunning then
//dprintfn "Cancelling request %d" id
pendingRequest.Cancel()
else
()
//dprintfn "Request %d has already finished" id
// Process other requests on worker thread
| Parser.NotificationMessage(method, json) ->
let n = Parser.parseNotification (method, json)
let task = processNotification n
processQueue.Add(ProcessNotification(method, task))
| Parser.RequestMessage(id, method, json) ->
let task = processRequest (Parser.parseRequest (method, json))
let cancel = new CancellationTokenSource()
processQueue.Add(ProcessRequest(id, task, cancel))
pendingRequests[id] <- cancel
| Parser.ResponseMessage(id, result) -> responseAgent.Post(Response(id, result))
processQueue.Add(Quit)
with e ->
dprintfn $"Exception in read thread {e}"
)
.Start()
// Process messages on main thread
let mutable quit = false
while not quit do
match processQueue.Take() with
| Quit -> quit <- true
| ProcessNotification(_, task) -> Async.RunSynchronously(task)
| ProcessRequest(id, task, cancel) ->
if cancel.IsCancellationRequested then
()
//dprintfn "Skipping cancelled request %d" id
else
try
match Async.RunSynchronously(task, 0, cancel.Token) with
| Some(result) -> respond (send, id, result)
| None -> respond (send, id, "null")
with :? OperationCanceledException ->
()
//dprintfn "Request %d was cancelled" id
pendingRequests.TryRemove(id) |> ignore
Environment.Exit(1)