diff --git a/server/src/server.ts b/server/src/server.ts index f6673703b..696676431 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -2,6 +2,7 @@ import process from "process"; import * as p from "vscode-languageserver-protocol"; import * as m from "vscode-jsonrpc/lib/messages"; import * as v from "vscode-languageserver"; +import * as rpc from "vscode-jsonrpc"; import * as path from "path"; import fs from "fs"; // TODO: check DidChangeWatchedFilesNotification. @@ -39,6 +40,9 @@ let projectsFiles: Map< > = new Map(); // ^ caching AND states AND distributed system. Why does LSP has to be stupid like this +// will be properly defined later depending on the mode (stdio/node-rpc) +let send: (msg: m.Message) => void = (_) => { }; + let sendUpdatedDiagnostics = () => { projectsFiles.forEach(({ filesWithDiagnostics }, projectRootPath) => { let content = fs.readFileSync( @@ -60,7 +64,7 @@ let sendUpdatedDiagnostics = () => { method: "textDocument/publishDiagnostics", params: params, }; - process.send!(notification); + send(notification); filesWithDiagnostics.add(file); }); @@ -78,7 +82,7 @@ let sendUpdatedDiagnostics = () => { method: "textDocument/publishDiagnostics", params: params, }; - process.send!(notification); + send(notification); filesWithDiagnostics.delete(file); } }); @@ -98,7 +102,7 @@ let deleteProjectDiagnostics = (projectRootPath: string) => { method: "textDocument/publishDiagnostics", params: params, }; - process.send!(notification); + send(notification); }); projectsFiles.delete(projectRootPath); @@ -167,7 +171,7 @@ let openedFile = (fileUri: string, fileContent: string) => { method: "window/showMessageRequest", params: params, }; - process.send!(request); + send(request); // the client might send us back the "start build" action, which we'll // handle in the isResponseMessage check in the message handling way // below @@ -216,7 +220,22 @@ let getOpenedFileContent = (fileUri: string) => { return content; }; -process.on("message", (msg: m.Message) => { +// Start listening now! +// We support two modes: the regular node RPC mode for VSCode, and the --stdio +// mode for other editors The latter is _technically unsupported_. It's an +// implementation detail that might change at any time +if (process.argv.includes("--stdio")) { + let writer = new rpc.StreamMessageWriter(process.stdout); + let reader = new rpc.StreamMessageReader(process.stdin); + // proper `this` scope for writer + send = (msg: m.Message) => writer.write(msg); + reader.listen(onMessage); +} else { + // proper `this` scope for process + send = (msg: m.Message) => process.send!(msg); + process.on("message", onMessage); +} +function onMessage(msg: m.Message) { if (m.isNotificationMessage(msg)) { // notification message, aka the client ends it and doesn't want a reply if (!initialized && msg.method !== "exit") { @@ -266,7 +285,7 @@ process.on("message", (msg: m.Message) => { message: "Server not initialized.", }, }; - process.send!(response); + send(response); } else if (msg.method === "initialize") { // send the list of features we support let result: p.InitializeResult = { @@ -290,7 +309,7 @@ process.on("message", (msg: m.Message) => { result: result, }; initialized = true; - process.send!(response); + send(response); } else if (msg.method === "initialized") { // sent from client after initialize. Nothing to do for now let response: m.ResponseMessage = { @@ -298,7 +317,7 @@ process.on("message", (msg: m.Message) => { id: msg.id, result: null, }; - process.send!(response); + send(response); } else if (msg.method === "shutdown") { // https://microsoft.github.io/language-server-protocol/specification#shutdown if (shutdownRequestAlreadyReceived) { @@ -310,7 +329,7 @@ process.on("message", (msg: m.Message) => { message: `Language server already received the shutdown request`, }, }; - process.send!(response); + send(response); } else { shutdownRequestAlreadyReceived = true; // TODO: recheck logic around init/shutdown... @@ -322,7 +341,7 @@ process.on("message", (msg: m.Message) => { id: msg.id, result: null, }; - process.send!(response); + send(response); } } else if (msg.method === p.HoverRequest.method) { let emptyHoverResponse: m.ResponseMessage = { @@ -338,9 +357,9 @@ process.on("message", (msg: m.Message) => { ...emptyHoverResponse, result: { contents: result.hover }, }; - process.send!(hoverResponse); + send(hoverResponse); } else { - process.send!(emptyHoverResponse); + send(emptyHoverResponse); } } else if (msg.method === p.DefinitionRequest.method) { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition @@ -361,9 +380,9 @@ process.on("message", (msg: m.Message) => { range: result.definition.range, }, }; - process.send!(definitionResponse); + send(definitionResponse); } else { - process.send!(emptyDefinitionResponse); + send(emptyDefinitionResponse); } } else if (msg.method === p.CompletionRequest.method) { let emptyCompletionResponse: m.ResponseMessage = { @@ -374,13 +393,13 @@ process.on("message", (msg: m.Message) => { let code = getOpenedFileContent(msg.params.textDocument.uri); let result = runCompletionCommand(msg, code); if (result === null) { - process.send!(emptyCompletionResponse); + send(emptyCompletionResponse); } else { let definitionResponse: m.ResponseMessage = { ...emptyCompletionResponse, result: result, }; - process.send!(definitionResponse); + send(definitionResponse); } } else if (msg.method === p.DocumentFormattingRequest.method) { // technically, a formatting failure should reply with the error. Sadly @@ -409,8 +428,8 @@ process.on("message", (msg: m.Message) => { method: "window/showMessage", params: params, }; - process.send!(fakeSuccessResponse); - process.send!(response); + send(fakeSuccessResponse); + send(response); } else { // See comment on findBscExeDirOfFile for why we need // to recursively search for bsc.exe upward @@ -425,8 +444,8 @@ process.on("message", (msg: m.Message) => { method: "window/showMessage", params: params, }; - process.send!(fakeSuccessResponse); - process.send!(response); + send(fakeSuccessResponse); + send(response); } else { let resolvedBscExePath = path.join(bscExeDir, c.bscExePartialPath); // code will always be defined here, even though technically it can be undefined @@ -454,13 +473,13 @@ process.on("message", (msg: m.Message) => { id: msg.id, result: result, }; - process.send!(response); + send(response); } else { // let the diagnostics logic display the updated syntax errors, // from the build. // Again, not sending the actual errors. See fakeSuccessResponse // above for explanation - process.send!(fakeSuccessResponse); + send(fakeSuccessResponse); } } } @@ -473,7 +492,7 @@ process.on("message", (msg: m.Message) => { message: "Unrecognized editor request.", }, }; - process.send!(response); + send(response); } } else if (m.isResponseMessage(msg)) { // response message. Currently the client should have only sent a response @@ -505,4 +524,4 @@ process.on("message", (msg: m.Message) => { } } } -}); +}