I’m implementing a Swift version of the Language Server Protocol. The way that it integrates within Visual Studio Code (VS Code) is via
stdout. That’s all fine and dandy. It also makes uses of a modified JSON-RPC message construct for its communication.
While testing out my server’s ability to handle commands coming in from
stdin, I was simply using
print() to output the response message. Anyhow, input a message, and the output was working great.
However, when I went to test it within VS Code, I would get the
initialize request, send a message back, and nothing. What I expected to have happen was for VS Code to start sending me more messages.
So what was the problem? I honestly had no idea.
Problem #1: From the LSP spec, it isn’t immediately obvious what the response messages should look like. Should it include the message header? Should it just have the JSON-RPC part? Is my message even formatted correctly? The spec calls for
\r\n instead of
\n, did I mess that up?
I go through and validate the message and output in all of the different permutations I can think of, but nothing. After spending some time digging around other LSP implementations, I come to the conclusion that I am indeed sending back the right message format, so what could it be?
Problem #2: Esoteric history and undocumented (or implied) behavior.
Ok, so if the message format is correct, maybe the output isn’t actually working as it looks like it is. So I run
mkfifo input, and
tail output. Let’s see what is happening.
cat initialize.lsp > input (my saved message content for an initialize request) gets my language server to handle the message, but no output.
It turns out that Swift’s
print() simply routes to the underlying
stdio output. Which, if you don’t know, does buffered or unbuffered output depending on what its actually being output to. In the case of the console, it’s output immediately. In the case of a file descriptor, it’s buffered.
It’s temporary because I actually need to write the proper version of the output code to ensure that I’m writing the content correct and only the number of bytes that are specified in the response message.
Here’s the thing, I actually knew about how
stdio buffers it’s output. However, when I looked at the print() documentation, I simply became complacent and assumed since it didn’t mention buffering that it indeed immediately wrote the content out. Later testing, of course, would prove otherwise.
The problem here is somewhat systemic of our programming culture. It came from a combination of unclear documentation (from two sources, nonetheless), and an assumption of knowledge that, even if the person knows, can forget to apply in certain contexts.
Hopefully this radar gets fixed: