How to Create an Integrated LSP Web Code Editor with Multi-Language Support

For a cloud development platform, a good Web IDEcode editor can greatly improve the user's coding experience, and an Web IDEimportant part of a code editor is the code editor.

There are currently a variety of code editors on the web to choose from, such as Ace, CodeMirror, Monacoand . The comparison of these three editors is introduced in detail in this article , so I won’t go into details here. In this article, we choose Monaco Editorto integrate LSP so that it can theoretically support all programming languages.

Original link: https://forum.laf.run/d/1027

What is LSP

LSP (Language Server Protocol), also known as Language Service Protocol, more specifically and popularly speaking, defines a set of specifications between the code editor and the language server, thus allowing the original

mnCorrespondence between editors and programming languages

become

mThe relationship between an editor and LSP, and the relationship between na programming language and LSP,

m*nThis reduces the development complexity m+nfrom

In addition to being friendly to editor developers and programming language developers, it is also more friendly to developers like us who try to make an editor support multiple languages. With such an editor in front of us, we can easily adapt to the vscodedesign vscodeof Ideas to realize our needs.

Preview

In this article, we will develop the smallest and most lightweight editor Demoas a demonstration. The architecture is very simple, that is, creating a front-end and a language server at the back-end. The two are transmitted Monaco Editorthrough vscode-ws-jsonrpcand services. The actual implementation of the The editor is as follows:WebSocketWebPython

Server side development

Before Webthe client can access the language service, we must first run a language service on the server. https://langserver.org/ This website contains many language service implementations.

Here we choose the language service officially maintained by Microsoft pyrightto provide language services

First create Expressthe server, configure the static file service, use fileURLToPathand dirnameto get the path of the current file, and set the service on 30000the port

const app = express();
const __filename = fileURLToPath(import.meta.url);
const dir = dirname(__filename);
app.use(express.static(dir));
const server = app.listen(30000);

Then we need to create a WebSocket Server. Pay attention to the noServer parameter here. If noServer is not specified, WebSocketServer will automatically create an http server to handle the upgrade of the browser's HTTP request to the WebSocket request.

const wss = new WebSocketServer({
    noServer: true,
});

Here we need to create our own HTTP server and manually handle the browser's upgrade request. The following code is how to listen to the upgrade event and process it.

server.on('upgrade',()=>{});

In the processing function, follow the following code to use WebSocket into the jsonrpc protocol, and start the language server to connect the two.

First build the path to the language server and find the location of the copyright package.

const baseDir = resolve(getLocalDirectory(import.meta.url));
const relativeDir = '../../../node_modules/pyright/dist/pyright-langserver.js';
const ls = resolve(baseDir, relativeDir); 

Create a connection to the language server and create a data connection to the WebSocket

const serverConnection = createServerProcess(serverName, 'node', [ls, '--stdio']);
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
const socketConnection = createConnection(reader, writer, () => socket.dispose());

Finally, use the forward function to forward the message from socketConnection to serverConnection, as follows:

forward(socketConnection, serverConnection, message => {
    if (Message.isRequest(message)) {
        console.log(`Received:`);
        console.log(message);
    }
    if (Message.isResponse(message)) {
        console.log(`Sent:`);
        console.log(message);
    }
    return message;
});

So we ran the language server and entered something casually into the front-end editor that will be written later in the article. You can see that the message is output in the terminal.

In this way, we have completed the development of the language server using copyright.

Web development

Next we develop the front-end content, still in the website above,

It can be seen that Monaco Editor also has a solution to support LSP, so we use the monaco-languageclient developed by TypeFox to develop integrated LSP with monaco.

First, use the initServices function of monaco-languageclient to initialize some services. The most important of them are the following four configurations, which define Monaco's language services and theme display.

await initServices({
    enableModelService: true,
    enableThemeService: true,
    enableTextmateService: true,
    enableLanguagesService: true,
})

Then create a WebSocket connection to the language server.

createWebSocket("ws://localhost:30000/pyright");

Creating this connection also requires the use of monaco-languageclient.

const createWebSocket = (url: string): WebSocket => {
    const webSocket = new WebSocket(url);
    webSocket.onopen = async () => {
        const socket = toSocket(webSocket);
        const reader = new WebSocketMessageReader(socket);
        const writer = new WebSocketMessageWriter(socket);
        languageClient = createLanguageClient({
            reader,
            writer
        });
        await languageClient.start();
        reader.onClose(() => languageClient.stop());
    };
    return webSocket;
};

const createLanguageClient = (transports: MessageTransports): MonacoLanguageClient => {
    return new MonacoLanguageClient({
        name: 'Pyright Language Client',
        clientOptions: {
            documentSelector: [languageId],
            errorHandler: {
                error: () => ({ action: ErrorAction.Continue }),
                closed: () => ({ action: CloseAction.DoNotRestart })
            },
            workspaceFolder: {
                index: 0,
                name: 'workspace',
                uri: monaco.Uri.parse('/tmp')
            },
            synchronize: {
                fileEvents: [vscode.workspace.createFileSystemWatcher('**')]
            }
        },
        connectionProvider: {
            get: () => {
                return Promise.resolve(transports);
            }
        }
    });
};

Next, you need to create a virtual file system as the input and output of the Monaco Editor instance.

const fileSystemProvider = new RegisteredFileSystemProvider(false);
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/test.py'), 'print("Hello, laf!")'));
registerFileSystemOverlay(1, fileSystemProvider);
const modelRef = await createModelReference(monaco.Uri.file('/test.py'));

Finally, create a Monaco Editor instance and perform some configuration.

createConfiguredEditor(document.getElementById('container')!, {
    model: modelRef.object.textEditorModel,
    automaticLayout: true,
    minimap: {
        enabled: false
    },
    scrollbar: {
        verticalScrollbarSize: 4,
        horizontalScrollbarSize: 8,
    },
    overviewRulerLanes: 0,
    lineNumbersMinChars: 4,
    scrollBeyondLastLine: false,
    theme: 'vs',
});

In this way we have completed the front end.

import Editor from './python/Editor';

function App() {

  return (
    <>
      <h2>monaco python lsp</h2>
      <div style={
   
   {height:"500px", width:"800px", border:"1px solid black", padding:"8px 0"}}>
        <Editor />
      </div>
    </>
  )
}

export default App

The effect is as follows

summary

It is still very difficult to deeply understand LSP and the working principles behind it, but fortunately there are excellent open source projects such as languageserver and languageclient to provide support, which allows us to have good code editing after just piecing together a few pieces of code. device effect. The next step is to use LSP to transform Laf 's Web IDE.

Since I have just come into contact with this piece of knowledge, it is inevitable that there will be errors and omissions in the article. I hope to communicate and make progress with everyone who reads this.

References

Guess you like

Origin blog.csdn.net/alex_yangchuansheng/article/details/132803462