Language Server Protocol, or LSP, aims at providing languages support services in a decentralized manner.
It was originally developed by Microsoft in 2015 as a communication standard to offer advanced features in their new editor, VS Code. The whole idea is to be able to expose services in a “generic” manner so that any editor can potentially benefit from language support services including code completion, code navigation, hovers, linting and more. As a specification it describes a JSON-RPC communication protocol between a server and a client. The client is either a standard text editor or the IDE (plugin) used by the developer, and the server is the process containing all the smarts about a given language. All of the services are handled by the server and exposed to the client via the protocol.
Once Microsoft released the standard things started moving pretty fast for Language Server Protocol. Companies like Red-Hat started contributing the standard and, by 2020, a lot of programming languages offer a LSP implementation including C, C++, Java, C#, Python, SQL, Rust, Go, COBOL, Fortran, and Lua.
So, now that I have told you what Language Server Protocol is, let me share why it’s important, how I use it every day, and why it is the key to reducing duplicated efforts in my daily developer life.
Language Server Protocol is All About Reducing Effort Duplication
One of the goals of LSP is solving a commonly recurring problem when supporting programming languages: the duplication of effort. Before Language Server Protocol, you had a choice of installing an IDE (Eclipse, Visual Studio, Netbeans, or IntelliJ) and configuring it to give you advanced features such as autocomplete, intelligent search, and code analysis. Embedded software developers would need to install flavored version of such IDEs to benefit from those features in a project targeted for particular MCU, for example, Atmel Studio, MCUXpresso, Kinetis Studio, or STM Cube. All those solutions ultimately aim at making the developer’s life easier, but at the cost of duplicated efforts since each solution has developed their own way of doing things for a given language or CPU/MCU.
Language support as a service was thus heterogeneous and LSP aimed at giving teams developing such services/features a unified way of exposing the features. Language Server Protocol is a JSON RPC protocol that allows the editor/IDE to talk to a language server.
Yet LSP also had an anticipated side effect: it de-ported and centralized the work and efforts in the right places. Take the example of C. The LLVM developers started working on an official server implementation: clangd. It also let other developers focus on the integration of the support into their platform/editor.
Language Server Protocol Needs a Server and a Client
In order to provide advanced support features for a given language, the server needs to be able to “understand” it and each language with Language Server Protocol support will provide a server implementation.
On the other side, you need a client to communicate with the server. The client is usually implemented as a plugin for an IDE or a text editor. While there can be more than one client for a given editor and language server, things are settling down and the ecosystem is clearing out with a few clients left standing above the rest.
See the diagram below for an example of client/server transaction:
A Word of Caution about Language Server Protocol
Bear in mind Language Server Protocol is not a silver bullet: although not bad, LSP is not perfect and comes with some inherent issues. Client developers, for example, noticed that different servers were behaving differently to certain requests, or assuming clients would handle responses in a certain way, expecting different requests at different times. On their side, server developers might rely on one client implementation to develop their server, leading to discrepancies between the expected and actual behavior. Some protocol extensions, too, such as CodeAction, can be server specific, making it hard for a client to write a generic handler for such responses.
But these issues primarily impact client and server developers, not LSP users. Still, there are some small issues from a user standpoint. Much of the time, Language Server Protocol will not fully work entirely well “out of the box” and will require some tweaking.
Moreover, not every server will be configured in the same way. For example, the C/C++ server will require a compilation database, while jdt.ls server will require a gradle setup. This exposes a gap that IDEs can fill as IDEs can use Language Server Protocol while abstracting or unifying such configuration processes. However, now it is necessary to choose which server and which client to install and, if you are using an IDE, chances are that those choices have already been made for you.
If you are using a text editor, you most certainly have to choose, but there can be multiple servers for any given language and multiple clients for a given editor. Unfortunately, the best server will be close to worthless if you do not use a good client that exposes all of its capabilities, and vice versa. The final issue – and perhaps the greatest point of concern – is that not every language benefits from the same level of support and some server implementations are in a state of abandon or seeing little activity. This does not mean they are not functional, but it does raise questions about their capacity to reach a state of maturity or remain futureproof in the absence of an alternative implementation.
Current Language Server Protocol servers and clients can be found here and here.
By separating the language support services into a server and editor integration into the client, Language Server Protocol has adopted a ‘divide and conquer’ approach to a common developer complaint. LSP has helped in defining a way language support services are developed, exposed and integrated, and allowed developers to focus on integration while putting very advanced features into the reach of regular text editors. LSP is not perfect, though, and there are issues waiting for fixes still. The imperfect protocol is not the main blocker; instead, some languages are left with incomplete, or abandoned server implementations. This can potentially leave Language Server Protocol heterogeneous and unsuitable for several development use cases. For example, users may not want to spend time understanding why a server is not working, or which one to use if there is more than one server available.
Some server implementers continue to rely on VSCode as the de-facto standard for validating their server, and this can be problematic if, in the future, VSCode-specific extensions are developed and injected into the protocol. However, there are a number of languages that continue to be well supported, and the technology has clearly benefited those languages. Moreover, “new” languages like Rust were able to leverage the protocol to offer support services in modern IDEs and text editors.
In the future, we can hope Language Server Protocol evolves to be fully generic, enforces or at least clarifies some implementation details and provides reference implementation samples for developers to work with. Some editors are already taking this path and started implementing their own LSP client rather than relying on a plugin ecosystem. It is not absurd to imagine a text editor shipping with an integrated LSP API. Language Server Protocol has made language support services more accessible, and many editors/IDE can benefit from it.