I want to highlight Python syntax errors in browser.
I discovered there is LSP implementation for Python and LSP client for Monaco editor.
Is there any way to connect them together?
I want to highlight Python syntax errors in browser.
I discovered there is LSP implementation for Python and LSP client for Monaco editor.
Is there any way to connect them together?
There is a way to connect them together!
It is all about the Language Server Protocol.
The first thing you need is a running server that will provide language-specific logic (such as autocompletion, validation, etc.).
As mentioned in your question, you can use palantir's python-language-server.
You can also find a list of existing language server implementations by language on langserver.org.
In LSP, client and server are meant to communicate via a JSON-RPC websocket.
You can use python-jsonrpc-server and execute this python script to launch a python language server on your device:
python langserver_ext.py
This will host the language server on ws://localhost:3000/python
.
Monaco is initially part of VSCode. Most existing LSP client parts for Monaco are thus initially meant for VSCode, so you will need to use a bit of nodejs and npm.
There exist a lot of modules to link monaco and an LSP client, some with vscode, some not - and it becomes very time-consuming to get this sorted out.
Here is a list of modules I used and finally got to work:
Now, the neat part: node modules are server-side javascript. Which means, you can't use them within a browser directly (see It is not possible to use RequireJS on the client (browser) to access files from node_modules.).
You need to use a build tool, like browserify to transpile your node modules to be usable by a browser:
.../node_modules/@codingame/monaco-languageclient/lib$ browserify monaco-language-client.js monaco-services.js connection.js ../../monaco-jsonrpc/lib/connection.js -r ./vscode-compatibility.js:vscode > monaco-jsonrpc-languageclient.js
This will create a file named monaco-jsonrpc-languageclient.js
, which we will use as a bundle for both monaco-languageclient and monaco-jsonrpc.
Notes:
-r ./vscode-compatibility.js:vscode
tells browserify to use the vscode-compatibility.js
file for every dependency to the vscode
module (see Unresolved dependencies to 'vscode' when wrapping monaco-languageclient).Now that you have a browser-compatible javascript file, you need to make needed components visible (ie. export them as window
properties).
In monaco-jsonrpc-languageclient.js
, search for places where MonacoLanguageClient, createConnection, MonacoServices, listen, ErrorAction, and CloseAction are exported. There, add a line to glabally export them:
(...)
exports.MonacoLanguageClient = MonacoLanguageClient;
window.MonacoLanguageClient = MonacoLanguageClient; // Add this line
(...)
exports.createConnection = createConnection;
window.createConnection = createConnection; // Add this line
(...)
(MonacoServices = exports.MonacoServices || (exports.MonacoServices = {}));
window.MonacoServices = MonacoServices; // Add this line
(...)
etc.
Do the same operation for normalize-url:
.../node_modules/normalize-url/lib$ browserify index.js > normalizeurl.js
In normalizeurl.js
, search for the place where normalizeUrl is exported. There (or, as default, at the end of the file), add a line to globally export it:
window.normalizeUrl = normalizeUrl;
And you can do the same operation for reconnecting-websocket, or use the amd version that is shipped with the module.
Include monaco-jsonrpc-languageclient.js
, normalizeurl.js
and the browserified or AMD reconnecting-websocket module on your page.
For faster loading time, you can also minify them with a minifying tool (like uglify-js).
Finally, we can create and connect the client:
// From https://github.com/TypeFox/monaco-languageclient/blob/master/example/src/client.ts
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
MonacoServices.install(monaco); // This is what links everything with your monaco editors.
var url = 'ws://localhost:3000/python';
// Create the web socket.
var webSocket = new ReconnectingWebSocket(normalizeUrl(url), [], {
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.3,
connectionTimeout: 10000,
maxRetries: Infinity,
debug: false
});
// Listen when the web socket is opened.
listen({
webSocket,
onConnection: function(connection) {
// create and start the language client
var languageClient = new MonacoLanguageClient({
name: 'Python Language Client',
clientOptions: {
// use a language id as a document selector
documentSelector: ['python'],
// disable the default error handler
errorHandler: {
error: () => ErrorAction.Continue,
closed: () => CloseAction.DoNotRestart
}
},
// create a language client connection from the JSON RPC connection on demand
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
}
}
});
var disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
}
});