Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monaco editor: Highlight syntax errors in Python

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?

like image 956
Pavel Avatar asked Aug 06 '26 07:08

Pavel


1 Answers

There is a way to connect them together!
It is all about the Language Server Protocol.

First step: the Language Server

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.

Second step: the Language Client

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:

  • @codingame/monaco-languageclient
  • @codingame/monaco-jsonrpc
  • normalize-url
  • reconnecting-websocket

Using server-side javascript on a browser

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:

  • The -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).
  • We browserify these modules as a single bundle, to avoid multiple inclusions of some dependencies.

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());
    }
});
like image 197
Astor Bizard Avatar answered Aug 07 '26 20:08

Astor Bizard