wylie

Teaching the Language Server Protocol to CodeMirror

March 23, 2019

In my ongoing work to improve the Popcode code editing environment, I came across the Language Server Protocol, which is widely integrated into desktop coding tools for features like autocompletion. The idea of connecting a language server to a browser has been tried before, specifically by TypeFox teaching the language server protocol to Microsoft’s Monaco editor, but I could not find any efforts to apply that to the CodeMirror editor.

This leads me to today, where I have created a library that connects a CodeMirror editor in a web browser to a language server, and automatically configures autocompletion, syntax checking, and go to definition support while being fully customizable. I spent a month creating a hosted demo environment to explain this functionality- when you connect to that environment, you will get assigned a new container to run your own language server.

Screenshot of the autocompletion functionality for Javascript/Typescript:

screenshot 2019-01-21 01 03 47

The library code is on Github under the ISC license. I welcome you to use the library in your own projects and to submit issues and pull requests you may have! As a solo developer on this, I’m also very excited to see if this code is useful before investing more in maintaining it.

The frontend of the demo environment is also available on Github here.

This version of the library requires a Web Socket connection to a language server, which is most similar to the inter-process communication of a language server running on your desktop. If your specific language server supports these features, they will be available in the browser as well:

There is an example project provided within the library to show how you might configure the library, but a simple configuration might look like this:

import * as CodeMirror from 'codemirror';
// You are required to install the show-hint addon
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/show-hint';

// Each adapter can have its own CSS
import 'lsp-editor-adapter/lib/codemirror-lsp.css';
import { LspWsConnection, CodeMirrorAdapter } from 'lsp-editor-adapter';

let editor = CodeMirror(document.querySelector('.editor'), {
  value: 'var code = "Hello world!";',

  // Optional: You can add a gutter for syntax error markers
  gutters: ['CodeMirror-lsp']
});

// Take a look at how the example is configured for ideas
let connectionOptions = {
  // Required: Web socket server for the given language server
  serverUri: 'ws://localhost:8080/html',
  // The following options are how the language server is configured, and are required
  rootUri: 'file:///path/to/a/directory',
  documentUri: 'file:///path/to/a/directory/file.html',
  documentText: () => editor.getValue(),
  languageId: 'html',
};

// The WebSocket is passed in to allow testability
let lspConnection = new LspWsConnection(editor)
  .connect(new WebSocket('ws://localhost:8080'));

// The adapter is what allows the editor to provide UI elements
let adapter = new CodeMirrorAdapter(lspConnection, {}, editor);

// To clean up the adapter and connection:
adapter.remove();
lspConnection.close();