Skip to content

Commit 4497c93

Browse files
jjleechenglou
andauthored
Add --stdio switch (#88)
* Add --stdio switch motivated by LSP clients such as emacs lsp-mode.el that don't support communication over node IPC Co-authored-by: Cheng Lou <[email protected]>
1 parent 8113017 commit 4497c93

File tree

1 file changed

+43
-24
lines changed

1 file changed

+43
-24
lines changed

server/src/server.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import process from "process";
22
import * as p from "vscode-languageserver-protocol";
33
import * as m from "vscode-jsonrpc/lib/messages";
44
import * as v from "vscode-languageserver";
5+
import * as rpc from "vscode-jsonrpc";
56
import * as path from "path";
67
import fs from "fs";
78
// TODO: check DidChangeWatchedFilesNotification.
@@ -39,6 +40,9 @@ let projectsFiles: Map<
3940
> = new Map();
4041
// ^ caching AND states AND distributed system. Why does LSP has to be stupid like this
4142

43+
// will be properly defined later depending on the mode (stdio/node-rpc)
44+
let send: (msg: m.Message) => void = (_) => { };
45+
4246
let sendUpdatedDiagnostics = () => {
4347
projectsFiles.forEach(({ filesWithDiagnostics }, projectRootPath) => {
4448
let content = fs.readFileSync(
@@ -60,7 +64,7 @@ let sendUpdatedDiagnostics = () => {
6064
method: "textDocument/publishDiagnostics",
6165
params: params,
6266
};
63-
process.send!(notification);
67+
send(notification);
6468

6569
filesWithDiagnostics.add(file);
6670
});
@@ -78,7 +82,7 @@ let sendUpdatedDiagnostics = () => {
7882
method: "textDocument/publishDiagnostics",
7983
params: params,
8084
};
81-
process.send!(notification);
85+
send(notification);
8286
filesWithDiagnostics.delete(file);
8387
}
8488
});
@@ -98,7 +102,7 @@ let deleteProjectDiagnostics = (projectRootPath: string) => {
98102
method: "textDocument/publishDiagnostics",
99103
params: params,
100104
};
101-
process.send!(notification);
105+
send(notification);
102106
});
103107

104108
projectsFiles.delete(projectRootPath);
@@ -167,7 +171,7 @@ let openedFile = (fileUri: string, fileContent: string) => {
167171
method: "window/showMessageRequest",
168172
params: params,
169173
};
170-
process.send!(request);
174+
send(request);
171175
// the client might send us back the "start build" action, which we'll
172176
// handle in the isResponseMessage check in the message handling way
173177
// below
@@ -216,7 +220,22 @@ let getOpenedFileContent = (fileUri: string) => {
216220
return content;
217221
};
218222

219-
process.on("message", (msg: m.Message) => {
223+
// Start listening now!
224+
// We support two modes: the regular node RPC mode for VSCode, and the --stdio
225+
// mode for other editors The latter is _technically unsupported_. It's an
226+
// implementation detail that might change at any time
227+
if (process.argv.includes("--stdio")) {
228+
let writer = new rpc.StreamMessageWriter(process.stdout);
229+
let reader = new rpc.StreamMessageReader(process.stdin);
230+
// proper `this` scope for writer
231+
send = (msg: m.Message) => writer.write(msg);
232+
reader.listen(onMessage);
233+
} else {
234+
// proper `this` scope for process
235+
send = (msg: m.Message) => process.send!(msg);
236+
process.on("message", onMessage);
237+
}
238+
function onMessage(msg: m.Message) {
220239
if (m.isNotificationMessage(msg)) {
221240
// notification message, aka the client ends it and doesn't want a reply
222241
if (!initialized && msg.method !== "exit") {
@@ -266,7 +285,7 @@ process.on("message", (msg: m.Message) => {
266285
message: "Server not initialized.",
267286
},
268287
};
269-
process.send!(response);
288+
send(response);
270289
} else if (msg.method === "initialize") {
271290
// send the list of features we support
272291
let result: p.InitializeResult = {
@@ -290,15 +309,15 @@ process.on("message", (msg: m.Message) => {
290309
result: result,
291310
};
292311
initialized = true;
293-
process.send!(response);
312+
send(response);
294313
} else if (msg.method === "initialized") {
295314
// sent from client after initialize. Nothing to do for now
296315
let response: m.ResponseMessage = {
297316
jsonrpc: c.jsonrpcVersion,
298317
id: msg.id,
299318
result: null,
300319
};
301-
process.send!(response);
320+
send(response);
302321
} else if (msg.method === "shutdown") {
303322
// https://microsoft.github.io/language-server-protocol/specification#shutdown
304323
if (shutdownRequestAlreadyReceived) {
@@ -310,7 +329,7 @@ process.on("message", (msg: m.Message) => {
310329
message: `Language server already received the shutdown request`,
311330
},
312331
};
313-
process.send!(response);
332+
send(response);
314333
} else {
315334
shutdownRequestAlreadyReceived = true;
316335
// TODO: recheck logic around init/shutdown...
@@ -322,7 +341,7 @@ process.on("message", (msg: m.Message) => {
322341
id: msg.id,
323342
result: null,
324343
};
325-
process.send!(response);
344+
send(response);
326345
}
327346
} else if (msg.method === p.HoverRequest.method) {
328347
let emptyHoverResponse: m.ResponseMessage = {
@@ -338,9 +357,9 @@ process.on("message", (msg: m.Message) => {
338357
...emptyHoverResponse,
339358
result: { contents: result.hover },
340359
};
341-
process.send!(hoverResponse);
360+
send(hoverResponse);
342361
} else {
343-
process.send!(emptyHoverResponse);
362+
send(emptyHoverResponse);
344363
}
345364
} else if (msg.method === p.DefinitionRequest.method) {
346365
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
@@ -361,9 +380,9 @@ process.on("message", (msg: m.Message) => {
361380
range: result.definition.range,
362381
},
363382
};
364-
process.send!(definitionResponse);
383+
send(definitionResponse);
365384
} else {
366-
process.send!(emptyDefinitionResponse);
385+
send(emptyDefinitionResponse);
367386
}
368387
} else if (msg.method === p.CompletionRequest.method) {
369388
let emptyCompletionResponse: m.ResponseMessage = {
@@ -374,13 +393,13 @@ process.on("message", (msg: m.Message) => {
374393
let code = getOpenedFileContent(msg.params.textDocument.uri);
375394
let result = runCompletionCommand(msg, code);
376395
if (result === null) {
377-
process.send!(emptyCompletionResponse);
396+
send(emptyCompletionResponse);
378397
} else {
379398
let definitionResponse: m.ResponseMessage = {
380399
...emptyCompletionResponse,
381400
result: result,
382401
};
383-
process.send!(definitionResponse);
402+
send(definitionResponse);
384403
}
385404
} else if (msg.method === p.DocumentFormattingRequest.method) {
386405
// technically, a formatting failure should reply with the error. Sadly
@@ -409,8 +428,8 @@ process.on("message", (msg: m.Message) => {
409428
method: "window/showMessage",
410429
params: params,
411430
};
412-
process.send!(fakeSuccessResponse);
413-
process.send!(response);
431+
send(fakeSuccessResponse);
432+
send(response);
414433
} else {
415434
// See comment on findBscExeDirOfFile for why we need
416435
// to recursively search for bsc.exe upward
@@ -425,8 +444,8 @@ process.on("message", (msg: m.Message) => {
425444
method: "window/showMessage",
426445
params: params,
427446
};
428-
process.send!(fakeSuccessResponse);
429-
process.send!(response);
447+
send(fakeSuccessResponse);
448+
send(response);
430449
} else {
431450
let resolvedBscExePath = path.join(bscExeDir, c.bscExePartialPath);
432451
// code will always be defined here, even though technically it can be undefined
@@ -454,13 +473,13 @@ process.on("message", (msg: m.Message) => {
454473
id: msg.id,
455474
result: result,
456475
};
457-
process.send!(response);
476+
send(response);
458477
} else {
459478
// let the diagnostics logic display the updated syntax errors,
460479
// from the build.
461480
// Again, not sending the actual errors. See fakeSuccessResponse
462481
// above for explanation
463-
process.send!(fakeSuccessResponse);
482+
send(fakeSuccessResponse);
464483
}
465484
}
466485
}
@@ -473,7 +492,7 @@ process.on("message", (msg: m.Message) => {
473492
message: "Unrecognized editor request.",
474493
},
475494
};
476-
process.send!(response);
495+
send(response);
477496
}
478497
} else if (m.isResponseMessage(msg)) {
479498
// response message. Currently the client should have only sent a response
@@ -505,4 +524,4 @@ process.on("message", (msg: m.Message) => {
505524
}
506525
}
507526
}
508-
});
527+
}

0 commit comments

Comments
 (0)