The Language Server Protocol in Java
Parts of this post are outdated.
The ls-api library has been superseded by LSP4J. Parts of this post refer to outdated API.The Language Server Protocol (LSP) is an interface for connecting arbitrary languages to development tools (IDEs). Such a connection enables important features like validation and code completion, which have become a matter of course in today’s IDEs. LSP is maintained by Microsoft and has a growing number of implementations. TypeFox offers an open-source Java binding named ls-api that can be used to connect existing language servers to an IDE or to implement a language server. In this post I’ll explain how this Java binding works and how you can employ it for your needs. LSP assumes there is an IDE with an open text editor, called the client, and a server providing support for a particular language. Depending on your use case, you may look at LSP and its Java binding from different perspectives:
- If you’re building a tool that includes editing a programming, scripting, or domain-specific language (a client), you can use LSP to add validation, code completion, mouse hover popups, and many more valuable features.
- If you’re designing a language or building IDE features for an existing language (a server), you can use LSP to make your work consumable by different tools.
- If you’re building a complete tool stack with editor and language support, you can use LSP to decouple the language-specific components from the rest of the application, e.g. to execute them on different machines.
The message classes
As documented in a GitHub repository, LSP consists of JSON structures which are sent via three types of messages: requests from client to server, responses from server to client, and notifications in either way. In the ls-api base project io.typefox.lsapi, each structure defined by the protocol is represented by a Java interface and an implementing class, where the interface declares getters for all properties of the structure, and the implementation adds setters in order to allow convenient construction of messages. In addition, there are builder classes for most of the structures that allow to build messages more conveniently. The interfaces, implementations, and builders are generated with the Xtend language using active annotations; this concept allows to maintain both Java artifacts from a single source.
Java API
The additional ls-api project io.typefox.lsapi.services defines an interface LanguageServer that includes the whole protocol specification from the client perspective. The different message types are realized as follows.
- For each request message there is a method with corresponding parameter type, e.g.
initialize(InitializeParams)
for the initialize request. The return type is always CompletableFuture, so the actual request processing is done asynchronously (similar to jQuery promises). - Result messages are realized through the CompletableFuture API, e.g. pass a callback to
handle(BiFunction)
to be notified of both success and failure of the request processing. - For each client-to-server message there is a method with corresponding parameter type, e.g.
didChange(DidChangeTextDocumentParams)
. As these messages don’t have any response, the return type isvoid
. - For each server-to-client message there is a method for registering a callback function, e.g.
onPublishDiagnostics(Consumer<PublishDiagnosticsParams>)
JSON conversion
With the message classes implementing the data structures of the protocol as described above, most of the conversion from and to JSON can be done using the generic capabilities of Gson. The MessageJsonHandler supplements this conversion with proper handling of the different message parameter types. In addition, the class LanguageServerProtocol supports reading messages from input streams and writing messages to output streams by handling the LSP header fields.
Connecting language servers via JSON
The ls-api project provides two classes that make use of the JSON conversion described above:
- JsonBasedLanguageServer connects a language server via I/O streams and makes it available through the Java API. This can be used to connect a server running in a different process with your client application, even if the server is not implemented in Java.
- LanguageServerToJsonAdapter is a wrapper for an existing language server implementation that adapts to the JSON-based protocol. This can be used to make your server consumable by a client running in a different process.
By connecting both classes with each other, e.g. through a socket channel, you get remote procedure calls bewteen a client and a server process.
Example: Xtext Language Server
The language development framework Xtext has a LanguageServer implementation that can connect any Xtext DSL to LSP clients such as Visual Studio Code or Eclipse Che. The following code demonstrates how to use this in a stand-alone process that communicates with a client via standard I/O.
public class ServerLauncher {
public static void main(String[fusion_builder_container hundred_percent="yes" overflow="visible"][fusion_builder_row][fusion_builder_column type="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none"][] args) {
try {
ServerLauncher launcher = Guice.createInjector(new ServerModule())
.getInstance(ServerLauncher.class);
launcher.run(System.in, System.out);
} catch (Exception e) {
e.printStackTrace();
}
}
@Inject LanguageServerImpl languageServer;
public void run(InputStream in, OutputStream out) throws Exception {
LanguageServerToJsonAdapter adapter = new LanguageServerToJsonAdapter(
languageServer);
adapter.connect(in, out);
adapter.join();
}
}
The Xtext language server is currently under development and will be available with the release 2.11 (fall 2016).
Summing up
The Language Server Protocol has the potential to reshape the common approach to development tool building: instead of reimplementing support for your language again and again on different tools with different platforms and APIs, you can now build a reusable language support module. The LSP Java binding ls-api opens up this new approach to the Java world, which can be a valuable step forward both for IDEs (Eclipse, IntelliJ IDEA, etc.) and for language frameworks such as Xtext.
About the Author
Dr. Miro Spönemann
Miro joined TypeFox as a software engineer right after the company was established. Five years later he stepped up as a co-leader and is now eager to shape the future direction and strategy. Miro earned a PhD (Dr.-Ing.) at the University of Kiel and is constantly pursuing innovation about engineering tools.