Dear brave implementer,
This is all my progress in the language server project. It is now in your hands. Good luck.
Language server request
wrapped in WebSocket
message e.g.
Content-Length: ...\r\n Same request but
\r\n as TCP instead of
{ "jsonrpc": "2.0", ... } WebSocket No change
+--------+ +------------+ +-------------+ +--------+
| |----------------------->| |---------------->| |------------------->| |
| CLIENT | | websockify | | beheader.sh | | SERVER |
| |<-----------------------| |<----------------| |<-------------------| |
+--------+ +------------+ +-------------+ +--------+
Server response wrapped Server response Language server
in WebSocket message with header response in TCP message
with binary payload part removed e.g. e.g.
{ "jsonrpc": "2.0", ... } Content-Length: ...\r\n
\r\n
{ "jsonrpc": "2.0", ... }
There are four parts to the project:
-
The Monaco client
The client sends and receives WebSocket messages. -
websockify
websockify receives WebSocket messages from the client, translates them to plain TCP, then sends them to the server, and vice versa. You should use the Node version of this program, not the Python one. (I couldn't get the Python one to work and neither could a lot of people online.) -
beheader.sh
beheader.sh is a shell script that receives messages from the server, removes the header part, and sends the remaining JSON part to the client. This is necessary because the client doesn't accept messages that have the header part, even though this is part of the language server spec. :( -
The language server
A normal language server. Works with TCP or stdio. I've been using the TCP version.
git submodule init && git submodule update
pushd websockify/other/js
npm install
popd
pushd javascript-typescript-langserver
npm install && npm run build
popd
pushd vscode-ws-jsonrpc
npm install
popd
pushd monaco-languageclient
npm install
pushd example && npm install
popd
popd
- Run
./run-language-server.sh
to start the language server. - Wait until the language server is listening for connections then run
./run-beheader.sh
in a new terminal. - Run
./run-websockify.sh
in a new terminal to start websockify. - Run
./run-client.sh
in a new terminal to start the client. - Navigate to
localhost:3000
in a browser window. You should be able to see the messages sent and received in the output of./run-language-server.sh
.
Starts the language server listening on port 2000.
Starts beheader.sh
. Proxies between language server on port 2000 and WebSocket traffic on port 2003.
Starts websockify. Listens for WebSocket traffic on ws://localhost:2089
.
Starts the example Monaco client. Access it in a browser window at localhost:3000
.
A collection of messages that are valid per the language server protocol and will also be accepted by the language server.
These files all use CRLF line endings because that's what the protocol specifies.
If you change the body of a message, the Content-Length
header will need updated. You can do this automatically by
running ./run-message-test.sh
.
Use this to manually test sending messages over WebSocket to the language server. This script processes all the messages
in ./messages
and dumps them in ./message-test/messages.js
. If you make any changes to the messages, you need to
rerun this command. After running this script, you can test messages by opening ./message-test/index.html
in a
browser window.
Open it in a browser to manually send messages to the language server. You need to run ./run-message-test.sh
first.
Language server messages should have a header part (usually just Content-Length
) and a JSON body
(https://microsoft.github.io/language-server-protocol/specification).
However, the client only sends and accepts messages without the header part. Therefore outgoing messages need to have the header part added, and incoming messages need to have the header part removed.
I've added a hack to add the header part to outgoing messages to the client itself. The only required header is
Content-Length
so my fix counts the length of the outgoing message and then prefixes that as a header.
Incoming messages have their header part removed by beheader.sh
.
WebSocket frames can have a text or binary payload, depending on the frame's opcode (https://datatracker.ietf.org/doc/rfc6455/).
For some reason the client only supports text payloads. It assumes the payload is a string and then crashes if it is actually a Blob. I've opened an issue on the appropriate library's GitHub (TypeFox/vscode-ws-jsonrpc#7).
websockify appears to always send WebSocket frames with a binary payload, and I can't find any option to send a text payload instead.
I've put a quick fix in vscode-ws-jsonrpc
.
Responses sent by the server do not end with a newline character.
This means tools that process input per line, such as sed
and read
(by default), won't work.
To manually read a message from the server, you'll need to read the length of the message from the Content-Length
header, then read that number of characters from the JSON part. beheader.sh
shows how to do this.
The server throws an error when it doesn't like a request, even if it's a valid request per the language server specification.
Unfortunately, the error messages are completely useless. It's usually something along the lines of "tried to access some property on an object but the object is undefined so it didn't work".
There is a collection of messages the server will accept in ./messages
which might help you out.
Otherwise, ask for help on Gitter.