This is a simple language server example in Python.
Supports:
- python (pyls)
- c++ (ccls or clangd)
First, Install ccls
(or clangd
) and confirm ccls --version
has output.
sudo apt-get -y ccls
sudo apt-get -y clang-format
Then install requirements:
pip3 install -r requirements.txt
Edit configuration app/config.yml
if needed. You can use templates.
debug: yes
host: 127.0.0.1
port: 3000 # change the port if in use
# ...
Run server.py
:
python3 -u server.py
The output should look like:
*******************************************
WebSocket Language Server For Monaco Editor
*******************************************
use config: app/config.yaml
current rootUri: app/cpp_workspace
all commands:
- python: pyls -v
- cpp: ccls --init={
"index":{
"onChange":true,
"trackDependency":2
}
}
Started Web Socket at:
- python: ws://127.0.0.1:3000/python
- cpp: ws://127.0.0.1:3000/cpp
Visit http://127.0.0.1:3000/log to see log.
Some website do not accept http websocket, so you need a SSL certificate. You may generate a self-signed certificate for 127.0.0.1, put the cert and key files in ./ssl
, and install it as a trusted root certificate, then turn on ssl in config.yml
.
To configure formatting in ccls
, just set .clang-format
in app/cpp_workspace
.
BasedOnStyle: Google
IndentWidth: 4
SortIncludes: false
Use monaco-languageclient
and @codingame/monaco-jsonrpc
.
const {
MonacoLanguageClient,
CloseAction,
ErrorAction,
MonacoServices,
createConnection,
} = require("monaco-languageclient");
import { listen } from "@codingame/monaco-jsonrpc";
// ...
To use ccls
/clangd
, files should sycronized to server. server.py
provide a /file
api to update files.
The client should:
- connect
ws://.../file
via WebSocket - send message
"get_rootUri"
to get absolute path ofrootUri
in MonacoServer
let webSocket = new WebSocket("ws://" + serverHost + "/file");
webSocket.onopen = () => {
webSocket.send(JSON.stringify({ type: "get_rootUri" }));
};
webSocket.onmessage = (ev) => {
let message = JSON.parse(ev.data);
if (message.result == "ok") {
rootUri = message.data;
} else {
// ...
}
webSocket.close();
};
- Use this path as
rootUri
option:
const { MonacoServices } = require("monaco-languageclient");
MonacoServices.install(monaco, { rootUri });
- Use
file://filename
uri to create model:
let model = monaco.editor.createModel(code, language, monaco.Uri.parse("file://" + filename));
- Update file if monaco editor changed (create if
filename
not exists)
monaco_model.onDidChangeContent((e) => {
if (lang == "cpp") {
console.log("try to update file");
updateFile(filename, monaco_model.getValue());
}
});
function updateFile(filename, code) {
let url = "ws://" + serverHost + "/file";
if (!!fileWebSocket && fileWebSocket.readyState == fileWebSocket.OPEN) {
fileWebSocket.send(JSON.stringify({ type: "update", filename, code }));
} else {
fileWebSocket = new WebSocket(url);
fileWebSocket.onopen = (ev) => {
ev.target.send(JSON.stringify({ type: "update", filename, code }));
};
fileWebSocket.onmessage = (ev) => {
let message = JSON.parse(ev.data);
if (message.result == "ok") {
console.log("update file success:", filename);
} else {
console.warn("update file failed:", ev);
}
};
}
}